본문 바로가기
programing/Networks

Asynchronous Notification IO 모델

by RedWiz 2015. 5. 22.

 

Asynchronous Notification IO Model.zip

 

 

- 동기(Synchronous)

> send()가 호출되는 순간 데이터의 전송이 시작되고

, send() 호출이 완료(반환)되는 순간 데이터 전송이 완료

> recv()가 호출되는 순간 데이터의 수신이 시작되고

, recv() 호출이 완료(반환)되는 순간 데이터 수신이 완료

( recv()같은 경우 데이터 받을 때 까지 무한 대기 상태이다 )

 

- 비동기(Asynchronous)

> 입출력 함수의 반환시점과 데이터 송수신의 완료시점이 일치하지 않은 경우

 

- 동기화 입출력 단점

> 입출력이 진행되는 동안 호출된 함수가 반환을 하지 않아서 다른 일을 할 수 없음

> 비동기화가 해결책, 동기 방식의 단점을 극복한 모델

 

- 비동기 Notification 입출력 모델에 대한 이해

> 입력 버퍼에 데이터가 수신되어서 데이터의 수신이 필요하거나, 출력버퍼가 비어서 데이터 전송이 가능한 상황의 알림

> Notification IO : IO와 관련해서 특정 상황이 발생했음을 알리는 것

대표적으로 select 방식

-> IO가 필요한, 또는 가능한 상황이 되는 시점이(간단히 말해서 IO관련 이벤트의 발생 시점이)

select()가 반환하는 시점과 일치

> 비동기 Notification IO : IO 상태에 관계 없이 반환이 이루어 지므로 IO 상태 변화를 별도로 관찰해야함

 

cf. select()도 사실 타임아웃 지정 가능

(기본적으로는 동기화된 NotificationIO 모델이지만 비동기와 유사한 형태 가능)

타임 아웃 후에 다시 IO의 상태변화를 확인 하기 위해서는 핸들을 다시 모아서 select()를 호출 해야함

 

- IO의 상태 변화

소켓 상태 변화       <----> 소켓에 대한 IO의 상태 변화

소켓 이벤트 발생    <----> 소켓에 대한 IO 관련 이벤트 발생

 

- 비동기 Notification IO 모델

WSAEventSelect()와 WSAAsyncSelect()가 있음

WSAAsyncSelect()는 발생한 이벤트를 수신할 윈도우의 핸들을 지정해야 함(UI와 관련)

 

- 비동기 Notification IO 함수

int WSAEventSelect(

SOCKET s,                         // 소켓 핸들

WSAEVENT hEventObject,    // 이벤트 발생 유무 확인을 위한 Event오브젝트 핸들

long lNetworkEvents            // 감시하고자 하는 이벤트의 유형

);    // 성공 : 0, 실패 : SOCKET_ERROR

 

> Event 오브젝트와 소켓을 연결하는 함수

> 이벤트 유형 (비트 연산자 이용 가능)

FD_READ         수신할 데이터가 존재

FD_WRITE        블로킹 없이 데이터 전송이 가능

FD_OOB          Out-of-band 데이터 수신

FD_ACCEPT    연결 요청

FD_CLOSE      연결의 종료 요청

> 단 하나의 소켓을 대상으로만 함수 호출

select()는 반환되고 나면 이벤트의 발생확인을 위해 다시 모든 핸들을 대상으로 재호출

WSAEventSelect는 소켓의 정보가 OS에 등록이 되어 등록된 소켓에 대해서 재호출 불필요

 

- manual-reset모드 Event 오브젝트의 또 다른 생성

> CreateEvent()는 auto-reset과 manual-reset중 하나 선택하여 생성 가능

> 여기서 필요한것은 manual-reset 모드이면서 non-signaled상태인 Event오브젝트

 

#define WSAEVENT    HANDLE

WSAEVENT WSACreateEvent(void);    // 성공 : 오브젝트 핸들, 실패 : WSA_INVALID_EVENT

 

- Event 오브젝트 종료

BOOL WSACloseEvnet(WSAEVENT hEvent);    // 성공 : TRUE, 실패 : FALSE

 

- Event 발생 유무 확인

DWORD WSAWaitForMultipleEvents(

DWORD cEvents,                      // signaled로 전이 여부 확인할 Event 오브젝트 개수

const WSAEVENT* lphEvents,    // Event 오브젝트의 핸들을 저장한 배열의 주소

BOOL fWaitAll,                          // TRUE : 모두 signaled 상태시, FALSE : 하나만 signaled 상태시

DWORD dwTimeout,                  // 1ms 단위로 타임 아웃 지정, WSA_INFINITE : 무한 대기

BOOL fAlertable                        // TRUE : alertable wait 상태로 진입

);    // 반환 값 - WSA_WAIT_EVENT_0 : signaled 상태가 된 Event 핸들이 저장된 인덱스

둘 이상일 경우 그 중 가장 작은 인덱스

WAIT_TIMEOUT : 타임아웃

 

> 전달할 수 있는 최대 Event 핸들 수가 64개로 제한

-> 그 이상의 핸들을 관찰할 경우 쓰레드의 생성을 통한 확장이나 배열을 구분해서 함수 두번 호출

cf. WSAWaitForMultipleEvents()가 동시에 관찰할 수 있는 최대 Event 오브젝트 수는 WSA_MAXIMUM_WAIT_EVENTS 상수에서 확인 가능, 변경될 가능성 있음

 

> 함수가 반환하는 정보는 signaled 상태로 전이된 Event 오브젝트의 첫 번째 인덱스 값이므로 전이된 모든 핸들 정보는 알 수 없지만 Event가 manual-reset 모드를 참고하면 확인 가능

 

ex)

int posInfo = WSAWaitForMultipleEvents(numOfSock, hEvnetArray, FALSE, WSA_INFINITE, FALSE);

int startIndex = posInfo - WSA_WAIT_EVENT_0;

for(int i = startIndex; i < numOfSock; ++i)

{

int signaledEventIndex = WSAWaitForMultipleEvents(1, &hEventArray[i], TRUE, 0, FALSE);

// TimeOut을 0으로 놓고 배열을 하나씩 탐색

// signaledEvnetIndex에 WAIT_TIMEOUT이 아닐 경우 signaled로 전이된 핸들 인덱스 값

}

 

- Event 종류 구분

int WSAEnumNetworksEvents(

SOCKET s,

WSAEVENT hEventObject,

LPWSANETWORKEVENTS lpNetworkEvents

);    // 성공 : 0, 실패 : SOCKET_ERROR

 

> manual-reset모드의 Event를 non-signaled 상태로 되돌림

(발생한 이벤트 유형을 확인한 다음 ResetEvent()를 호출할 필요 없음)

 

typedef struct _WSANETWORKEVENTS

{

long lNetworkEvents;

int iErrorCode[FD_MAX_EVENTS];

} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

 

> WSANETWORKEVENTS 구조체에 이벤트 정보가 담김

> 수신할 데이터가 존재하면 FD_READ, 연결요청이 있는 경우 FD_ACCEPT

 

ex)

WSANETWORKEVENTS netEvents;

WSAEnumNetworkEvents(hSocks, hEvents, &netEvents);

if(netEvents.lNetworkEvnets & FD_ACCEPT)

{    /* FD_ACCEPT 이벤트 발생 */    }

if(netEvents.lNetworkEvnets & FD_READ)

{    /* FD_READ 이벤트 발생 */    }

if(netEvents.lNetworkEvnets & FD_CLOSE)

{    /* FD_CLOSE 이벤트 발생 */    }

 

> 오류발생 정보는 iErrorCode에 담김

* FD_XXX 관련 오류 : iErrorCode[FD_XXX _BIT]에 0 이외에 값

-> FD_READ 관련 오류 : iErrorCode[FD_READ_BIT]에 0 이외에 값

-> FD_WRITE 관련 오류 : iErrorCode[FD_WRITE_BIT]에 0 이외에 값

 

ex)

WSANETWORKEVENTS netEvents;

WSAEnumNetworkEvents(hSocks, hEvents, &netEvents);

if(netEvents.iErrorCode[FD_READ_BIT] != 0)

{    /* FD_READ 이벤트 관련 오류 */    }