※ Rookiss님의 [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 강의를 보고 정리한 글입니다.
overlapped는 non-blocking + aync 방법으로 진행된다. 이번엔 Send와 Recv만 Overlapped 모델을 적용 시켜볼 것이다.
비동기 입출력을 지원하는 소켓 생성 + 통지 받기 위한 이벤트 생성\
비동기 입출력 함수 실행(WSASend, WSARecv) ⇒ 만든 이벤트 객체를 넘겨줌
⇒ 실행 한 이후 바로 비동기 작업이 완료되지 않으면, WSA_IO_PENDING 오류 코드나 나옴.
⇒ 운영체제는 작업이 완료되면 이벤트 객체를 signaled 상태로 만들어서 완료 상태를 알려줌
WSAWaitForMultipleEvents함수 호출해서 이벤트 객체의 signal 판별
WSAGetOverlappedResult 호출해서 비동기 입출력 결과 확인 및 데이터 처리
overlapped활용을 위해 Session 구조체에 Overlapped를 추가해준다.
struct Session {
SOCKET socket = INVALID_SOCKET;
char recvBuffer[BUFSIZE] = {};
int32 recvBytes = 0;
int32 sendBytes = 0;
WSAOVERLAPPED overlapped = {};
};
소켓 실행부터 listen까지의 과정이다.
WSAData wsaData;
if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
return 0;
SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == INVALID_SOCKET)
return 0;
u_long on = 1;
if (::ioctlsocket(listenSocket, FIONBIO, &on) == INVALID_SOCKET)
return 0;
SOCKADDR_IN serverAddr;
::memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
serverAddr.sin_port = ::htons(7777);
if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
return 0;
if (::listen(listenSocket, SOMAXCONN) == SOCKET_ERROR)
return 0;
accept 처리는 non-blocking + sync 방법으로 진행해준다. (이번엔 Recv와 Send만 aync로 처리)
while (true) {
SOCKADDR_IN clientAddr;
int32 addrLen = sizeof(clientAddr);
SOCKET clientSocket;
while (true) {
clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket != INVALID_SOCKET)
break;
if (::WSAGetLastError() == WSAEWOULDBLOCK)
continue;
// 에러
return 0;
}
Session session = Session{ clientSocket };
WSAEVENT wsaEvent = ::WSACreateEvent();
session.overlapped.hEvent = wsaEvent;
cout << "Client Connected! " << endl;
while (true) {
WSABUF wsaBuf;
wsaBuf.buf = session.recvBuffer;
wsaBuf.len = BUFSIZE;
DWORD recvLen = 0;
DWORD flags = 0;
if (::WSARecv(clientSocket, &wsaBuf, 1, &recvLen, &flags, &session.overlapped, nullptr) == SOCKET_ERROR) {
if (::WSAGetLastError() == WSA_IO_PENDING) {
// Pending
::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, FALSE);
::WSAGetOverlappedResult(session.socket, &session.overlapped, &recvLen, FALSE, &flags);
}
else {
//문제 상황
break;
}
}
}
Pending
WSARecv를 실행 했을 때 바로 실행이 성공하는 경우가 드물게 있을 것 이다.
하지만 그렇지 않는 상황에선 WSA_IO_PENDING 이라는 오류가 뜨게된다.
그 상황에선 WSAWaitForMultipleEvents를 이용해 이벤트가 signaled가 될 때 까지 기다려주고
WSAGetOverlappedResult를 통해 결과 값을 받아준다.
Client에선 Send를 진행해보자.
char sendBuffer[100] = "Hello World";
WSAEVENT wsaEvent = ::WSACreateEvent();
WSAOVERLAPPED overlapped = {};
overlapped.hEvent = wsaEvent;
while (true) {
WSABUF wsaBuf;
wsaBuf.buf = sendBuffer;
wsaBuf.len = 100;
DWORD sendLen = 0;
DWORD flags = 0;
if(::WSASend(clientSocket, &wsaBuf, 1, &sendLen, flags, &overlapped, nullptr) == SOCKET_ERROR){
if (::WSAGetLastError() == WSA_IO_PENDING) {
//Pending
::WSAWaitForMultipleEvents(1, &wsaEvent, TRUE, WSA_INFINITE, FALSE);
::WSAGetOverlappedResult(clientSocket, &overlapped, &sendLen, FALSE, &flags);
}
else {
//ERROR
break;
}
}
Send도 마찬가지로 진행한다.
현재 송/수신을 WSABUF를 통해 Buffer를 지정하고 사용해 주고 있다. 송/수신이 진행 중일 땐 WSABUF를 건드리는 것은 괜찮지만 Buffer를 건드려선 안된다. Buffer를 건들이게 될 경우 의도한 것과 달리 송/수신 될 수 있기 때문이다.