Asynchronous Notification IO 모델
Asynchronous Notification IO 모델
WSAEventSelect 함수와 Notification
#include <winsock2.h>
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);
// 성공 시 0, 실패 시 SOCKET_ERROR 반환
s
: 관찰대상인 소켓의 핸들 전달hEventObject
: 이벤트 발생유무의 확인을 위한 Event 오브젝트의 핸들 전달.lNetworkEvents
: 감시하고자 하는 이벤트의 유형 정보전달.
즉, WSAEventSelect 함수는 매개변수 s에 전달된 핸들의 소켓에서 lNetworkEvents에 전달된 이벤트 중 하나가 발생하면, hEventObject에 전달된 핸들의 커널 오브젝트를 signaled
상태로 바꾸는 함수이다.
때문에 이 함수를 가리켜 “Event 오브젝트와 소켓을 연결하는 함수”라고 하기도 한다.
세번째 인자인 lNetworkEvents
에 전달할 수 있는 이벤트의 종류는 다음과 같다.
FD_READ
: 수신할 데이터가 존재하는가?FD_WRITE
: 블로킹 없이 데이터 전송이 가능한가?FD_OOB
: Out-of-Band 데이터가 수신되었는가?FD_ACCEPT
: 연결 요청이 있었는가?FD_CLOSE
: 연결의 종료가 요청되었는가?
select 함수는 여러 소켓을 대상으로 호출이 가능한데, WSAEventSelect 함수는 하나의 소켓을 대상으로만 호출이 가능하다.
select 함수는 반환되고 나면 이벤트의 발생확인을 위해서 또 다시 모든 핸들(파일 디스크립터)을 대상으로 재호출해야 되지만, WSAEventSelect 함수호출을 통해서 전달된 소켓의 정보는 운영체제에 등록이 되고, 이렇게 등록된 소켓에 대해서는 WSAEventSelect 함수의 재호출이 불필요하다.
다음의 내용을 추가로 알아야 한다.
WSAEventObject
함수의 두 번째 인자(WSAEVENT) 전달을 위한 오브젝트의 생성 방법WSAEventObject
함수호출 이후의 이벤트 발생 확인 방법- 이벤트 발생이 확인된 경우, 발생된 이벤트의 유형을 확인하는 방법
‘manual-reset 모드 Event 오브젝트’의 또다른 생성 방법
이전에는 CreateEvent 함수를 이용해서 Event 오브젝트를 생성하였다.
바로 manual-reset 모드이면서 non-signaled
상태인 Event 오브젝트를 생성하려면 다음 함수를 이용하는 것이 편리하다.
#include <winsock2.h>
WSAEVENT WSACreateEvent(void);
// 성공 시 Event 오브젝트 핸들, 실패 시 WSA_INVALID_EVENT 반환
#define WSAEVENT HANDLE
로, WSAEVENT는 HANDLE과 같다.
위의 함수를 통해서 생성된 Event 오브젝트의 종료는 다음과 같이 별도로 마련되어 있다.
#include <winsock2.h>
BOOL WSACloseEvent(WSAEVENT hEvent);
// 성공 시 TRUE, 실패 시 FALSE 반환
이벤트 발생유무의 확인
WSACreateEvent
함수호출 이후를 고민할 차례이다. 이벤트 발생유무의 확인을 위해서는 Event 오브젝트를 확인해야 한다.
참고로 이는 매개변수가 하나 더 많다는 것을 제외하면, WaitForMultipleObjects
함수와 동일하다.
#include <winsock2.h>
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT* lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
// 성공 시 이벤트 발생 오브젝트 관련 정보, 길패 시 WSA_INVALID_EVENT 반환
cEvents
:signaled
상태로의 전이여부를 확인할 Event 오브젝트의 개수 정보 전달.lphEvents
: Event 오브젝트의 핸들을 저장하고 있는 배열의 주소 값 전달.fWaitAll
: TRUE 전달 시 모든 Event 오브젝트가 signaled 상태일 때 반환, FALSE 전달 시 하나만 signaled 상태가 되어도 반환.dwTimeout
: 1/1000초 단위로 타임아웃 지정, WSA_INFINITE 전달 시signaled
상태가 될 때까지 반환하지 않는다.fAlertable
: TRUE 전달 시, alertable wait 상태로의 진입(이는 추후 설명한다.)- 반환 값: 반환된 정수 값에서 상수 값 WSA_WAIT_EVENT_0를 빼면, 두 번째 매개변수로 전달된 배열을 기준으로,
signaled
상태가 된 Event 오브젝트의 핸들이 저장된 인덱스가 계산된다. 만약에 둘 이상의 Event 오브젝트가signaled
상태로 전이되었다면, 그 중 작은 인덱스 값이 계산된다. 그리고 타임아웃이 발생하면 WAIT_TIMEOUT이 반환된다.
이는 소켓의 이벤트 발생에 의해서 Event 오브젝트가 signaled 상태가 되어야 반환하는 함수이므로 소켓의 이벤트 발생여부를 확인하기에 좋은 함수이다.
그러나 반환 값을 통해 알 수 있는 건 첫번째 핸들의 인덱스 뿐이므로, 다음과 같이 여러번의 반복문 호출을 통해서 모든 오브젝트의 핸들 정보를 알 수 있다. 반복 호출이 가능한 이유는 ‘manual-reset’이기 때문이다.
int posInfo, startIdx, i;
. . . . .
posInfo = WSAWaitForMultipleEvents(numOfSock, hEventArray, FALSE, WSA_INFINITE, FALSE);
startIdx = posInfo - WSA_WAIT_EVENT_0;
. . . . .
for(i = startIdx ; i < numOfSock ; i++)
{
int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArray[i], TRUE, 0, FALSE);
. . . . .
}
비동기 Notification IO 모델에서 Event 오브젝트가 ‘manual-reset’ 모드여야 하는 이유를 위에서 알 수 있다.
이벤트 종류의 구분
WSAWaitForMultipleEvents
함수를 통해서 signaled
항태로 전이된 Event 오브젝트까지 알아낼 수 있게 되었으니, 이제 마지막으로 해당 오브젝트가 signaled 상태가 된 원인을 확인해야 한다. 그리고 이를 위해서 다음 함수를 소개한다. 이 함수의 호출을 위해서는 signaled
상태의 Event 오브젝트 핸들뿐만 아니라, 이와 연결된(WSAEventSelect
함수 호출에 의해), 이벤트 발생의 주체가 되는 소켓의 핸들도 필요하다.
#include <winsock2.h>
int WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
);
// 성공 시 0, 실패 시 SOCKET_ERROR 반환
s
: 이벤트가 발생한 소켓의 핸들 전달.hEventObject
: 소켓과 연결된(WSAEventSelect 함수호출에 의해),signaled
상태인 Event 오브젝트의 핸들 전달.lpNetworkEvents
: 발생한 이벤트의 유형정보와 오류정보로 채워질 WSANETWORKEVENTS 구조체 변수의 주소 값 전달.
위 함수는 manual-reset 모드의 Event 오브젝트를 non-signaled
상태로 되돌리기까지 하니, 발생한 이벤트의 유형을 확인한 다음에, 별도로 ResetEvent 함수를 호출할 필요가 없다. 위 함수와 관련있는 구조체 WSANETWORKEVENTS를 소개하겠다.
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
위의 구조체 멤버 lNetworkEvents에는 발생한 이벤트의 정보가 담긴다.
여기의 lNetworkEvents
에 전달할 수 있는 이벤트의 종류 참조.
따라서 다음과 같은 방식으로 발생한 이벤트의 종류를 확인할 수 있다.
WSANETWORKEVENTS netEvents;
. . . . .
WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
if(netEvents.lNetworkEvents & FD_ADDEPT)
{
// FD_ACCEPT 이벤트 발생에 대한 처리
}
if(netEvents.lNetworkEvents & FD_READ)
{
// FD_READ 이벤트 발생에 대한 처리
}
if(netEvents.lNetworkEvents & FD_CLOSE)
{
// FD_CLOSE 이벤트 발생에 대한 처리
}
오류 발생에 대한 정보는 구조체 멤버로 선언된 배열 iErrorCode에 담긴다. 확인하는 방법을 정리하면 다음과 같다.
- 이벤트
FD_READ
관련 오류가 발생하면 iErrorCode[FD_READ_BIT]에 0 이외의 값 저장 - 이벤트
FD_WRITE
관련 오류가 발생하면 iErrorCode[FD_WRITE_BIT]에 0 이외의 값 저장
따라서 다음의 형태로 오류검사를 진행하면 된다.
WSANETWORKEVENTS netEvents;
. . . . .
WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
. . . . .
if(netEvents.iErrorCode[FD_READ_BIT] != 0)
{
// FD_READ 이벤트 관련 오류 발생
}
비동기 Notification I/O 모델 에코 서버 구현
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#define BUF_SIZE 100
void CompressSockets(SOCKET hSockArr[], int idx, int total);
void CompressEvents(WSAEVENT hEventArr[], int idx, int total);
void ErrorHandling(char *msg);
int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hServSock, hClntSock;
SOCKADDR_IN servAdr, clntAdr;
SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT newEvent;
WSANETWORKEVENTS netEvents;
int numOfClntSock=0;
int strLen, i;
int posInfo, startIdx;
int clntAdrLen;
char msg[BUF_SIZE];
if(argc!=2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hServSock=socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family=AF_INET;
servAdr.sin_addr.s_addr=htonl(INADDR_ANY);
servAdr.sin_port=htons(atoi(argv[1]));
if(bind(hServSock, (SOCKADDR*) &servAdr, sizeof(servAdr))==SOCKET_ERROR)
ErrorHandling("bind() error");
if(listen(hServSock, 5)==SOCKET_ERROR)
ErrorHandling("listen() error");
newEvent=WSACreateEvent();
if(WSAEventSelect(hServSock, newEvent, FD_ACCEPT)==SOCKET_ERROR)
ErrorHandling("WSAEventSelect() error");
hSockArr[numOfClntSock]=hServSock;
hEventArr[numOfClntSock]=newEvent;
numOfClntSock++;
while(1)
{
posInfo=WSAWaitForMultipleEvents( //첫번째 발생한 이벤트 index
numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE);
startIdx=posInfo-WSA_WAIT_EVENT_0; //상수값을 빼서 index를 구함
for(i=startIdx; i<numOfClntSock; i++)
{
int sigEventIdx=WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE);
if((sigEventIdx==WSA_WAIT_FAILED || sigEventIdx==WSA_WAIT_TIMEOUT))
{
//그 다음 시그널을 탐지
continue;
}
else
{
sigEventIdx=i; //어떤 이벤트인지 확인 및 처리
WSAEnumNetworkEvents(hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents);
if(netEvents.lNetworkEvents & FD_ACCEPT)
{
if(netEvents.iErrorCode[FD_ACCEPT_BIT]!=0)
{
puts("Accept Error");
break;
}
clntAdrLen=sizeof(clntAdr);
hClntSock=accept(hSockArr[sigEventIdx], (SOCKADDR*)&clntAdr, &clntAdrLen);
newEvent=WSACreateEvent(); // 이벤트 객체를 생성
WSAEventSelect(hClntSock, newEvent, FD_READ|FD_CLOSE);
hEventArr[numOfClntSock]=newEvent;
hSockArr[numOfClntSock]=hClntSock;
numOfClntSock++;
puts("connected new client...");
}
if(netEvents.lNetworkEvents & FD_READ)
{
if(netEvents.iErrorCode[FD_READ_BIT]!=0)
{
puts("Read Error");
break;
}
strLen=recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0);
send(hSockArr[sigEventIdx], msg, strLen, 0);
}
if(netEvents.lNetworkEvents & FD_CLOSE)
{
if(netEvents.iErrorCode[FD_CLOSE_BIT]!=0)
{
puts("Close Error");
break;
}
WSACloseEvent(hEventArr[sigEventIdx]);
closesocket(hSockArr[sigEventIdx]);
numOfClntSock--;
CompressSockets(hSockArr, sigEventIdx, numOfClntSock);
CompressEvents(hEventArr, sigEventIdx, numOfClntSock);
}
}
}
}
WSACleanup();
return 0;
}
// 소켓에 대한 정보 삭제
void CompressSockets(SOCKET hSockArr[], int idx, int total){
int i;
for(i=idx; i<total; i++)
hSockArr[i]=hSockArr[i+1];
}
void CompressEvents(WSAEVENT hEventArr[], int idx, int total){
int i;
for(i=idx; i<total; i++)
hEventArr[i]=hEventArr[i+1];
}
void ErrorHandling(char *msg){
fputs(msg, stderr);
fputc('\n', stderr);
exit(1);
}