WSAEventSelect 모델이란
지난 번에 썼던 Select 모델과 같이 소켓 입출력 시 성공할 때까지 대기하거나 재시도하는 상황을 해결해주는 방법이다.
그러나 Select 모델과 달리 이벤트 객체를 생성해서 소켓을 관리해준다. 또한 Select모델과 달리 매 번 감시 소켓 목록을 초기화하는 등의 작업이 필요 없다. 또한 동기 방식이었던 Select 모델과 달리 비동기 방식의 모델이다.
여기서 말하는 이벤트란 커널에 존재하는 비트 플래그 같은 것이다. 커널에 bool 형 변수가 있고, 이를 껐다가 켰다가 (Signal, NonSignal 상태) 하면서 그 상태를 통지받는다고 생각하면 된다.
함수들
- WSACreateEvent : 이벤트 생성
- WSACloseEvent : 이벤트 삭제
- WSAWaitForMultipleEvents : 신호 상태를 감지한다. 즉 소켓들이 동작을 실행할 준비가 될 때까지 대기하다가, 준비 됐음을 감지했을 때, 소켓 목록에서 준비된 소켓의 인덱스를 반환한다.
- Time Limit를 매개변수로 넘겨주어 최대로 대기할 시간을 설정할 수 있다.
- 매개변수로 받은 소켓 목록 중 단 한 개만 준비돼도 반환할 지, 아니면 목록의 모든 소켓이 준비돼야 반환할 지 여부도 매개변수로 넘겨서 설정할 수 있다.
- WSAEnumNetworkEvents : 구체적인 네트워크 이벤트 알아내기. WSAWaitForMultipleEvents 함수로 이벤트가 Signaled 되었음을 감지하고 반환됐을 때, 해당 소켓이 어떤 작업을 하려고 했었는지(소켓에 연결된 이벤트가 어떤 속성을 가지고 있었는지) 확인하는 함수이다.
- 선택적으로 소켓에 연결된 이벤트 객체를 매개변수로 넘겨주어 초기화(non Signaled 상태로 만들기) 시켜줄 수 있다.
- WSAEventSelect : 이벤트 와 소켓을 연동한다. 연동된 소켓은 자동으로 NonBlocking Socket이 된다. 연동할 때 어떤 동작이 준비됐을 때 이벤트가 Signaled될 것인지 이벤트의 속성을 설정해야 한다. 매개변수로 아래 옵션들을 설정해서 넘겨주면 된다.
- FD_ACCETP : 접속한 클라이언트가 있음 -> accept
- FD_READ : 데이터 수신 가능 -> recv
- FD_WRITE : 데이터 송신 가능 -> send
- FD_CLOSE : 상대가 접속 종료함
- FD_CONNECT : 통신을 위한 연결 완료
- FD_OOB : Out Of Band
방법
우선 기본적인 EventSelect 모델의 진행 방식을 설명하겠다.
- 소켓을 만든다.
- WSACreateEvent 함수로 이벤트 객체를 만든다.
- WSAEventSelect 함수로 1. 에서 만든 소켓과 2. 에서 만든 소켓을 연결한다. 이 때 소켓이 어떤 동작을 해야할 것인지 이벤트 속성을 설정한다.
- WSAWaitForMultipleEvents 를 호출하여 3.에서 연동한 이벤트 객체가 Signal될 때 까지 대기한다.
- 4.에서 호출한 WSAWaitForMultipleEvents함수가 반환되면 WSAEnumNetworkEvents 함수를 호출하여 Signal된 이벤트 객체가 어떤 이벤트 속성을 가지고 있었는지 조사한다.
- 5. 에서 조사한 이벤트 속성에 따라 소켓의 동작을 실행한다.
이 것이 EventSlect 모델의 동작 방식이다. 그림으로 표현하면 아래와 같을 것이다.
이제 각 소켓마다 적절한 설정과 동작만 수행해주면 된다.
예를 들어 Listen 작업을 하는 Listener 소켓은 위의 3. 과정에서 WSAEventSelect 함수의 3 번째 매개변수로 FD_ACCETP 를 넘겨주고, 6. 과정에서 accept 함수를 호출해주면 된다.
::WSAEventSelect(listenSocket, listenEvent, FD_ACCEPT | FD_CLOSE)
사용 코드
vector<WSAEVENT> wsaEvents;
vector<Session> sessions;
sessions.reserve(100);
WSAEVENT listenEvent = ::WSACreateEvent();
wsaEvents.push_back(listenEvent);
sessions.push_back(Session{ listenSocket });
if (::WSAEventSelect(listenSocket, listenEvent, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR)
return 0;
while (true)
{
int32 index = ::WSAWaitForMultipleEvents(wsaEvents.size(), &wsaEvents[0], FALSE, WSA_INFINITE, FALSE);
if (index == WSA_WAIT_FAILED)
continue;
index -= WSA_WAIT_EVENT_0;
//::WSAResetEvent(wsaEvents[index]);
WSANETWORKEVENTS networkEvents;
if (::WSAEnumNetworkEvents(sessions[index].socket, wsaEvents[index], &networkEvents) == SOCKET_ERROR)
continue;
// Listener 소켓 체크
if (networkEvents.lNetworkEvents & FD_ACCEPT)
{
// Error-Check
if (networkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
continue;
SOCKADDR_IN clientAddr;
int32 addrLen = sizeof(clientAddr);
SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
if (clientSocket != INVALID_SOCKET)
{
cout << "Client Connected" << endl;
WSAEVENT clientEvent = ::WSACreateEvent();
wsaEvents.push_back(clientEvent);
sessions.push_back(Session{ clientSocket });
if (::WSAEventSelect(clientSocket, clientEvent, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)
return 0;
}
}
// Client Session 소켓 체크
if (networkEvents.lNetworkEvents & FD_READ || networkEvents.lNetworkEvents & FD_WRITE)
{
// Error-Check
if ((networkEvents.lNetworkEvents & FD_READ) && (networkEvents.iErrorCode[FD_READ_BIT] != 0))
continue;
// Error-Check
if ((networkEvents.lNetworkEvents & FD_WRITE) && (networkEvents.iErrorCode[FD_WRITE_BIT] != 0))
continue;
Session& s = sessions[index];
// Read
if (s.recvBytes == 0)
{
int32 recvLen = ::recv(s.socket, s.recvBuffer, BUFSIZE, 0);
if (recvLen == SOCKET_ERROR && ::WSAGetLastError() != WSAEWOULDBLOCK)
{
// TODO : Remove Session
continue;
}
s.recvBytes = recvLen;
cout << "Recv Data = " << recvLen << endl;
}
// Write
if (s.recvBytes > s.sendBytes)
{
int32 sendLen = ::send(s.socket, &s.recvBuffer[s.sendBytes], s.recvBytes - s.sendBytes, 0);
if (sendLen == SOCKET_ERROR && ::WSAGetLastError() != WSAEWOULDBLOCK)
{
// TODO : Remove Session
continue;
}
s.sendBytes += sendLen;
if (s.recvBytes == s.sendBytes)
{
s.recvBytes = 0;
s.sendBytes = 0;
}
cout << "Send Data = " << sendLen << endl;
}
}
// FD_CLOSE 처리
if (networkEvents.lNetworkEvents & FD_CLOSE)
{
// TODO : Remove Socket
}
}
주의사항
- 소켓 1개당 이벤트 1개를 만들어야함.
- 소켓은 자동 넌블로킹모드로 바뀜.
- accept 함수를 통해 반환된 소켓은 listenSocket과 같은 속성을 가짐. 즉 accept 함수로 반환된 clientSocket은 다시 FD_READ 혹은 FD_WRITE 등의 이벤트를 다시 등록해야한다.
- 이벤트 발생 시 그에 따른 적절한 조치를 취해주기 전 까지는 다시 이벤트가 발생하지 않음. 즉 read 이벤트가 발생하면 해당 이벤트와 연결된 소켓으로 read 작업을 하기 전 까지 다시 그 이벤트가 발생할 일은 없음.
- Select 모델과 마찬가지로 WSAWaitForMultipleEvents 함수 호출 한 번에 최대 64개의 이벤트와 소켓을 감시할 수 있음.
'네트워크, 서버' 카테고리의 다른 글
Completion Port 모델 (0) | 2022.04.30 |
---|---|
Overlapped 모델(콜백함수) (0) | 2022.04.30 |
Select model (0) | 2022.04.28 |
Socket Options (0) | 2022.04.28 |
NAT(Network Address Translation) (0) | 2022.02.21 |