본문 바로가기

TCP-IP 소켓 프로그래밍

비동기 Notification IO 모델을 이해해보자

비동기(Asynchronous)??


비동기는 "동시에 일어나지 않음" 이라는 뜻을 가지고 있다. 반대로 동기는 "동시에 일어남" 이라는 뜻을 가지고 있다.

동기 Notification IO에서 대표적인 것은 Select 모델이 있다. 

그럼 도대체 뭐가 동시에 일어난다는걸까????

그건 바로 함수의 "완료"와 "반환"이다.

동기 함수
비동기

비동기는 함수 호출 후 바로 반환되기 때문에 오래걸리는 작업이더라도 기다리지 않고 바로 다음 코드를 실행 시킬 수 있다.

비동기 Notification IO 모델


그럼 도대체 비동기Notification IO 모델은 어떤걸 비동기로 처리할까?

앞에서 동기 Notification IO 모델의 대표적인 예시는 Select라고했다. Select함수는 동기함수로 IO 관찰 대상을 등록하고, I상태 변화가 발생할 때 까지 다음 코드로 넘어갈 수 없다.

비동기 Notification IO 모델은 관찰 대상 등록, 상태 변화 확인 두 가지로 함수가 나뉘어있다. 대상을 등록하는 부분을 비동기로 처리한다.

그럼 어떤 식으로 IO의 상태변화를 알 수 있을까?

IO와 이벤트 오브젝트를 같이 관리하여, 이벤트 오브젝트를  통해 상태 변화를 알린다.

 

WSAEventSelect 모델

WSAEventSelect모델은 비동기 Notification IO 모델이다. 세 가지 함수를 통해 구현할 수 있다.

  1. 관찰 대상 등록 함수
  2. 이벤트 발생 유무 확인 함수
  3. 발생한 이벤트 종류 구별 함수

관찰 대상 등록 함수

#include<winsock2.h>

int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);

→ 성공 시 0, 실패 시 SOCKET_ERROR를 반환함.

s 관찰 대상인 소켓
hEventObject 이벤트 발생유무 확인을 위한 Event 오브젝트
lNetworkEvents 감시하고자 하는 이벤트 유형

이 함수를 통해 소켓과 이벤트를 등록해두면 이벤트 발생 시, hEventObject로 전달된 Event오브젝트가 signaled상태로 바뀐다. (Event 오브젝트는'WSACreateEvent'함수로 생성하고, 'WSACloseEvent'함수로 종료해줘야한다!)

 

이벤트 종류

FD_READ 수신할 데이터가 있을 때
FD_WRITE 블로킹 없이 데이터가 전송 가능할 때
FD_OOB Out-of-band 데이터가 수신 되었을 때
FD_ACCEPT 연결요청이 있을 때
FD_CLOSE 연결 종료 요청이 있을 때

 

어? WSAEventSelect함수는 select와 다르게 여러 소켓을 한 번에 등록 못하네?

맞다. 이 부분에 있어선 WSAEventSelect가 안좋아 보일 수 있다. 하지만 select는 반환 후 한 번 더 모든 소켓을 등록해주어야하지만, WSAEventSelect함수를 실행하면 운영체제에 소켓의 정보가 등록되어 재호출 해줄 필요가 없다!

이벤트 발생유무 확인 함수

#include <winsock2.h>

DWORD WSAWaitForMultipleEvents
(DWORD cevents, const WSAEVENT* lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable);

→ 성공 시 이벤트 발생 오브젝트 관련정보, 실패 시 WSA_INVALID_EVENT를 반환함

cEvents signaled 상태로 전이여부를 확인할 Event 오브젝트 개수
lphEvents Events 오브젝트를 저장하고 있는 배열의 주소 값
fWaitAll 모든 오브젝트 대기 여부
- TRUE 시 모든 오브젝트가 signaled일 때 반환
- FALSE는 하나의 오브젝트만 signaled여도 반환
dwTimeout 타임 아웃 값. 무한 대기는 WSA_INFINITE
fAlertable alterable wait 상태 진입 여부
반환 값 - 반환 값에 WSA_WAIT_EVENT_0이라는 상수 값을 빼면 배열의 인덱스가 계산됨.
- 여러 Event가 signaled상태라면 가장 작은 인덱스의 값이 반환됨
- 타임 아웃 시 WAIT_TIMEOUT이 반환됨

인자들로 Event오브젝트들은 배열로 관리한다는 것을 알 수 있다. Socket 또한 배열로 관리해 똑같은 인덱스로 참조하며 관리한다!

SOCKET SocketArr[64];
WSAEVENT EventArr[64];

....

//signaled상태 가져오기
int minIdx = WSAWaitForMultipleEvents(n,hEventArr,FALSE, WSA_INFINITE, FALSE); 

int socketIdx = minIdx - WSA_WAIT_EVENT_0; // 인덱스 계산

SocketArr[socketIdx] ~~~~ // 인덱스로 참조

 

그럼 어떻게 signaled로 전환된 Event 오브젝트들을 모두 알 수 있을까?

한 번의 호출로는 모두 알 수 없다! 첫 번째 인덱스만 반환하기 때문이다. 하지만 Event 오브젝트가 manual-reset(수동으로 non-signaled상태로 만들어줘야하는 모드) 상태라면 이러한 방법을 사용할 수 있다.

int minIdx, startIdx;

minIdx = WSAWaitForMultipleEvents(numOfSock, hEventArray, FALSE, WSA_INFINITE, FALSE);
startIdx = minIdx - WSA_WAIT_EVENT_0;
....

for(int i = startIdx; i <numOfSock; i++){
	int sigEventIdx = WSAWaitForMultipleEvents(1, &EventArr[i], TRUE, 0, FALSE);
    .....
}

i 번째 부터 검사하여 1개 씩 살펴볼 수 있다. (Timeout이 0이므로 바로 반환됨) 이 모든 것은 Event 오브젝트가 manual-reset모드 이기에 가능한 일이다. 이것으로 비동기 Notification IO 모델에서 Event 오브젝트가 manual-reset 모드여야 하는 이유를 알 수 있다.

 

발생한 이벤트 종류 구별 함수

#include <winsock2.h>

int WSAEnumNetworkEvents(SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);

→ 성공 시 0, 실패 시 SOCKET_ERROR 반환

s 이벤트 발생한 소켓
hEventObject 소켓과 연결된 signaled상태 event 오브젝트
lpNetworkEvents 발생한 이벤트 유형과 오류 정보로
채워질 WSANETWORKEVNETS 구조체

이벤트의 유형과 오류 정보를 알기 위해 구조체가 만들어져있다.

typedef struct _WSANETWORKEVENTS
{
	long lNetworkEvents;
    int iErrorCode[FD_MAX_EVENTS]
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

lNetworkEvents에 이벤트의 정보가 담기고 iErrorCode에 오류의 정보가 담긴다.

 

어떻게 쓰지?

이벤트 유형의 경우 이벤트 등록 때 쓰였던 것으로 비트 and 연산을 진행하면된다.

WSANETWORKEVENTS netEvents;
.....

WSAEnumNetworkEvents(hSock, hEvent, &netEvents);
if(netEvents.lNetworkEvents & FD_ACCEPT) {
......

 

오류 정보의 경우 iErrorCode에 FD_XXX_BIT 인덱스를 통해 참조할 수 있다. 오류가 발생한 경우 0 이외의 값이 들어간다.

......

if(netEvnts.iErrorCode[FD_READ_BIT]!=0)
{
	// FD_READ 이벤트 오류 발생
}

이로써 모든 함수를 알아봤다. 이벤트를 등록하고(배열로 관리!!),  이벤트 발생유무를 확인한 후, 유형을 구별해 그에 맞는 처리를 하면 모델을 완성 시킬 수 있다! 소켓을 CLOSE할 경우 관리하던 배열에서 제거하는 것을 잊으면 안된다.

 

단점

  • WSAWaitForMultipleEvents함수가 동시에 관찰할 수 있는 최대 Event 오브젝트의 개수가 제한되어있다. 현재 64로 정의되어있지만, 새로운 버전의 운영체제가 등장하면 바뀔 수 있으니 주의하자!

 

마무리


처음 봤을 때 이해 되지 않는 부분이 있었다.

"WSAWaitForMultipleEvents함수에서 블로킹이 되는데 왜 이걸 비동기 모델이라고 부르는거지?"

지금 생각하면 엄청 멍청한 고민이었다. WSAEventSelect 모델은 이벤트 감지가 비동기로 이루어지는 것이아닌 이벤트 감지 대상을 등록, 감지를 나눠 등록 과정을 비동기하는 것이 핵심인데 이상한 쪽으로 고민하고 있었다.

 

이로써 비동기가 무엇인지, 비동기 Notification IO 모델은 무엇인지 알아봤다!

참고