비동기(Asynchronous) 입출력
- 입출력 함수의 호출 시점이 데이터의 송수신이 시작되는 시기
- 그러나, 호출된 함수는 이내 반환을 하고, 내부적으로는 계속해서 입출력이 진행되는 방식의 함수 호출
- 데이터 전송의 시작 및 완료의 시기가 일치하는 호출방식의 동기 입출력의 호출된 동안에 다른 작업을 할 수 단점 보완
비동기 Notification 입출력 모델에 대한 이해
- 동기 Notificatoin
- 입출력의 Notification(알림) 이 동기적으로 이루어지는 것
- select 함수를 반복을 통해 입출력이 완료 가능한 상태인지를 계속 체크 - 비동기 Notification
- 입출력의 Notification(알림)이 비동기적으로 이루어지는 것
- select 함수의 비동기 버전이 WSAEventSelect 함수
- WSAEventSelect 함수는 입출력의 완료에 상관없이 무조건 반환. -> 완료를 확인하기 위한 별도의 과정 필요
- WSAEventSelect 함수를 이용하면, IO의 상태변화를 명령한 다음, 이후에 기타 작업을 진행 다음에 I/O의 상태변화를 확인할 수 있다. - WSAEventSelect 함수와 Notification
- 소켓 하나당 한 번의 함수 호출이 진행 -> 한번 등록된 소켓은 select 함수와 달리 매 함수호출 때마다 재등록 필요x
- 소켓 매개변수 s에 전달된 핸들의 소켓에서 INetworkEvents에 전달 된 이벤트 중 하나가 발생하면, hEventObject에 전달된 핸들의 커널 오브젝트를 signaled 상태로 바꾸는 함수
- manual - reset 모드 Event 오브젝트의 또 다른 생성방법
#include <winsock2.h>
WSAEVENT WSACreateEvent(void);
//성공 시 Event 오브젝트 핸들, 실패 시 WSA_INVALID_EVENT 반환
BOOL WSACloseEvent(WSAEVNET hEvent);
//성공 시 TRUE, 실패 시 FALSE 반환
- 이벤트 발생유무의 확인에 사용되는 함수
- 발생한 이벤트 위치를 반환!
- 이벤트 종류의 구분을 위한 함수
- hEventObject와 연결된 s소켓에 발생한 이벤트의 정보를 IpNetworkEvents를 통해 전달
- InetworkEvent에는, 수신할 데이터가 존재하면 FD_READ가 저장
- 연결요청이 있는 경우 FD_ACCEPT 저장
- IErrorCode에는 이벤트 FD_XXX관련 오류 발생 시 iErrorCode[FD_XXX_BIT]에 0 이외의 값이 저장
비동기 Notification I/O 모델의 에코 서버 구현
- 소켓 생성시 WSACreateEvent() 함수 이용 -> 이벤트 객체를 생성
- WSAEventSelect() 함수를 이용하여 소켓과 이벤트 객체를 연결, 처리할 네트워크 이벤트를 등록
- WSAWaitForMultipleEvents() 함수 이용. -> 이벤트 발생 유무 확인
- WSAEnumNetworkEvents()해당 객체가 신호상태가 된 원인 확인 (이벤트 종류 확인)
#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);
}
또 다른 비동기 select
- WSAAsynSelect
- 커널에 등록해두면 이벤트 발생시 자동으로 메시지가 날아오는 동일한 방식
- WSAsynSelect를 사용하기 위해서는 이벤트를 수신할 윈도우의 핸들(UI 관련 이벤트) 지정 필요
(WSAEventSelect는 이벤트 오브젝트 시그널을 변경시키는 방식)
- 즉, 윈도우 메시지(UI 관련 이벤트)를 통지 방식으로 사용하기 때문에 윈도우 프로시저(WndProc)에서 만 사용가능