WSAEventSelect

WSAEventSelect 함수는 FD_XXX 네트웍 이벤트와 연관되는 이벤트 객체를 지정하기위해 사용되는 함수입니다.

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

 

Parameters

s
[입력] 대상 소켓 기술자

hEventObject
[입력] FD_XXX 네트웍 이벤트와 연관되는 이벤트 객체를 명시한 핸들

lNetworkEvents
[입력] 어플리케이션에서 발생시키고자 하는 FD_XXX 네트웍 이멘트의 비트조합

 

Remarks

WSAEventSelect 함수는 선택된 FD_XXX 네트웍 이벤트(INetworkEvents)와 연관될 이벤트 객체(hEventObject)를 명시하는데 사용하는 함수입니다. 이벤트 객체가 명시될 소켓은 s 매개변수입니다. 이벤트 객체는 등록된 네트웍 이벤트가 발생했을 때 설정됩니다.

WSAEventSelect 함수는 WSAAsyncSelect 함수와 매우 유사하게 동작합니다. 차이점이라면, 등록된 네트웍 이벤트가 발생됐을 때 동작되는 부분이죠. WSAAsyncSelect 함수는 명시된 윈도즈 메시지가 메시지 큐에 포스팅 되는 형태이고, WSAEventSelect 함수는 관련된 이벤트 객체를 설정하고, 내부적인 네트웍 이벤트의 발생을 남기는 형태입니다.

어플리케이션은 대기하거나 네트웍 이벤트 객체를 폴링하기 위해 WSAWaitForMultipleEvents 함수를 사용할 수 있습니다. 그리구, WSAEnumNetworkEvents 함수를 사용하여 내부적으로 발생한 네트웍 이벤트의 내용을 얻어낼 수 있습니다. 즉, 이렇게 함으로써 발생한 네트웍 이벤트가 어떠한건지 알아낼 수 있다는 거죠.

WSAEventSelect 함수는 INetworkEvents 의 값과는 상관없이 자동으로 소켓 s 를 비동기 모드로 설정합니다. 소켓을 다시 동기모드로 되돌려놓는 방법에 대해 알고 싶으며, ioctlsocket/WSAIoctl 함수를 살펴보도록 하세요.

INetworkEvents 매개변수는 아래에 나열된 값들을 OR 연산해서 얻은 값으로 구성됩니다.

Value Meaning
FD_READ 데이터를 읽을(수신) 수 있다라는 통지를 받고자 할 때
FD_WRITE 데이터를 쓸(송신) 수 있다라는 통지를 받고자 할 때
FD_OOB out-of-band 데이터의 수신에 대한 통지를 받고자 할 때
FD_ACCEPT 접속요구에 대한 통지를 받고자 할 때
FD_CONNECT 접속작업이나 다중 "join" 연산의 통지를 받고자 할 때
FD_CLOSE 소켓이 닫혔다라는 통지를 받고자 할 때
FD_QOS 소켓 Quality of Service(QOS)가 바뀌었다는 통지를 수신하고자 할 때
FD_GROUP_QOS 소켓 Group Quality of Service(GROUP_QOS)가 바뀌었다는 통지를 수신하고자 할 때
FD_ROUTING
_INTERFACE_CHANGE
특정한 목적지에 대해서 라우팅 인터페이스가 변경되었다는 통지를 받고자 할 때
FD_ADDRESS_LIST
_CHANGE
소켓 어드레스 집합에 대해서 로컬 어드레스 리스트가 변경했다는 통지를 받고자 할 때

한 소켓에 대해서 WSAEventSelect 함수를 호출하게되면, 같은 소켓에 대해서 이전에 WSAAsyncSelect 함수나 WSAEventSelect 함수의 호출을 무시하게 되고, 이전에 설정되었던 내부적 네트웍 이벤트 등록을 없애버립니다. 한 예로, 읽기와 쓰기 네트웍 이벤트 양쪽을 받고자 하는 상태를 만들기 위해서는 아래의 코드와 같이 FD_READ와 FD_WRITE를 양쪽다 같이 설정해 주어야 합니다. 달리 말하자면, 먼저 FD_READ하고 나중에 FD_WRITE 한다고 읽기와 쓰기 모두에 대해서 통지 메시지를 받지는 않는다는 점을 꼭 기억하세요. 이렇게 하면, FD_READ는 무시되고 FD_WRITE만 통지 받게 됩니다.

rc = WSAEventSelect(s, hEventObject, FD_READ|FD_WRITE);

다른 네트웍 이벤트에 대해서 다른 이벤트 객체를 명시하는 방법은 불가능 합니다. 아래의 코드는 제대로 동작하지 않을 것입니다. 즉, 두 번째 구문의 호출이 첫 번째 구문의 호출의 효과를 무시해 버릴 것입니다. 그리고, 단지 FD_WRITE 네트웍 이벤트 만이 hEventObject2에 대해서 발생할 것입니다.

rc = WSAEventSelect(s, hEventObject1, FD_READ);
rc = WSAEventSelect(s, hEventObject2, FD_WRITE);

관련된 그리고 한 소켓에 대해서 선택된 네트웍 이벤트를 취소하기 위해서 INetworkEvent 를 0으로 설정해서 WSAEventSelect 함수를 호출하면 됩니다. 이렇게 하면, hEventObject 매개변수는 앞으로 무시될 것입니다.

rc = WSAEventSelect(s, hEventObject, 0);

closesocket 함수로 소켓을 종료하게 되면 소켓에 대해서 WSAEventSelect로 지정된 네트웍 이벤트를 취소하게 됩니다. 하지만, 어플리케이션은 반드시 WSACloseEvent 함수를 호출해서 이벤트 객체와 리소스를 반환 해 줘야 합니다.

리슨소켓으로 접속허용(accepted)되어 생성된 소켓은 리슨소켓과 같은 속성을 가지게 됩니다. 예를들어 리슨소켓이 FD_ACCEPT, FD_READ, 그리고 FD_WRITE 속성을 가지도록 설정되어 있다면, 접속허용된 소켓도 리슨 소켓과 같은 FD_ACCEPT, FD_READ, FD_WRITE 속성을 가지게 된다는 것입니다. 새로 생성된 접속허용된 소켓에 다른 네트웍 이벤트를 설정하고 싶으면, WSAEventSelect 함수를 호출하여 새로운 정보로 설정해 주어야 합니다.

selectWSAAsyncSelect 함수의 경우와 같이 WSAEventSelect 함수는 데이터 전송/수신 연산(send/recv)이 일어날 수 있는 시점을 결정하는데 자주 사용됩니다. 하지만, 100%정확하게 이러한 시점에서 데이터를 전송하거나 수신할 수 있는 것은 아닙니다. 예외적인 상황이 발생 할 수도 있다는 거죠. 예를들어 데이터를 수신해도 된다는 FD_READ 이벤트를 받고나서 데이터를 수신했을 때 WSAEWOULDBLOCK 이라는 에러가 발생 할 수도 있다는 의미입니다. 그러므로 튼튼한 어플리케이션을 만들려면, 이러한 예외적인 상황에 대비할 수 있는 준비를 해야 합니다. 아래에 이러한 상황이 일어날 수 있는 시나리오를 한번 생각해 봤습니다. 참고하세요.

1. 데이터가 소켓 s 에 도착 했습니다. 윈도즈 소켓은 WSAEventSelect 이벤트 객체를 설정합니다.

2. 어플리케이션은 몇몇 다른 작업을 진행하고 있는 도중입니다.

3. 작업을 진행하고 있는동안 ioctlsocket( s, FIONREAD...)를 호출했다고 해봅시다. 그러면, 읽을 수 있는 데이터가 있다는 사실을 알게 되겠죠.

4. 어플리케이션은 데이터를 읽기 위해서 recv(s, ...)를 호출 할 것입니다.

5. 하던 작업이 끝나면, 1.에서 설정된 이벤트 객체가 있으므로 데이터를 읽을 수 있는 상태라는 것을 알 수 있게 됩니다.

6. 어플리케이션은 데이터를 읽기 위해서 recv(s, ...)를 호출할 것이고, 이미 이 데이터는 읽혀졌지 때문에 WSAEWOULDBLOCK 에러를 내면서 실패 하게 됩니다.

이벤트 객체와 연관된 신호와 네트웍 이벤트의 발생을 계속 성공적으로 발생시키려면, 네트웍 이벤트가 통지 되었을 때 이벤트 객체와 연관된 재활성화(re-enabling) 함수를 명시적으로 호출해 주어야 합니다. 그래야만, 다음에 발생하는 네트웍 이벤트나 신호를 받을 수 있게 됩니다. 아래의 표에 각 네트웍 이벤트에 해당하는 재활성화 함수(re-enabling function)을 나열해 보았습니다.

Network Event Re-enabling function
FD_READ recv, recvfrom, WSARecv, 또는 WSARecvFrom
FD_WRITE send, sendto, WSASend, 또는 WSASendTo
FD_OOB recv, recvfrom, WSARecv, 또는 WSARecvFrom
FD_ACCEPT accept 또는 WSAAccept 에러코드가 WSATRY_AGAIN이 발생했을 경우는 예외
FD_CONNECT 없당
FD_CLOSE 없당
FD_QOS SIO_GET_QOS 컴맨드로 호출된 WSAIoctl
FD_GROUP_QOS SIO_GET_GROUP_QOS 컴맨드로 호출된 WSAIoctl
FD_ROUTING
_INTERFACE_CHANGE
SIO_ROUTING_INTERFACE_CHANGE 컴맨드로 호출된 WSAIoctl
FD_ADDRESS_LIST
_CHANGE
SIO_ADDRESS_LIST_CHANGE 컴맨드로 호출된 WSAIoctl

FD_READ, FD_OOB, 그리고 FD_ACCEPT 이벤트에 대한 메시지 포스팅 방법은 "level-triggered"라고 합니다. 무슨 말이냐구요? 만약 재활성화 루틴이 호출되고, 이와 관련된 상태가 루틴 호출후에도 여전히 남아 있다면, 메시지는 어플리케이션에 다시 포스팅 됩니다. 이러한 포스팅 방법으로 어플리케이션은 메시지 구동 방식이 됩니다. 아직 이해가 잘 않되시죠? 아래의 순서를 한번 생각해 보세요... 지금 제가 한말을 쉽게 이해 할 수 있을 겁니다.

1. 네트웍 전송/수신 스택이 소켓에 100 바이트의 데이터를 수신받고, 윈속이 FD_READ 메시지를 포스팅 했습니다.

2. 어플리케이션이 recv( s, buffer, 50, 0 )을 호출해서 50바이트를 수신했습니다.

3. 읽을 데이터가 여전히 남아있는 동안 FD_READ 메시지가 포스팅 됩니다.

이러한 구조상에서, 어플리케이션은 하나의 FD_READ 메시지에 대해서 모든 가능한 데이터를 읽어낼 필요가 없습니다. 각각의 FD_READ 메시지에 대해서 하나의 recv를 사용하는 것이 적절한 방법입니다. 만약 어플리케이션이 하나의 FD_READ에 대해서 여러개의 recv를 호출할 경우, 여러개의 FD_READ 메시지를 수신할 수도 있습니다. 이러한 어플리케이션에서는 recv함수를 호출하기전에 WSAAsyncSelect 함수를 FD_READ 이벤트를 셋팅하지 않는 방법으로 FD_READ 메시지 수신을 막아 버려야할 필요가 있습니다.

FD_QOS 그리고 FD_GROUP_QOS 이벤트는 "edge triggered"로 간주됩니다. 메시지는 Quality Of Service가 바뀌었을 때 정확히 한번 포스팅 됩니다. 게다가 메시지는 프로바이더가 QOS에서의 다른 변화를 감지하든지, 아니면, 어플리케이션이 소켓에 대한 QOS를 재설정할 때 까지 메시지는 수신되지 않습니다.

FD_ROUTING_INTERFACE_CHANGE 메시지는 SIO_ROUTING_INTERFACE_CHANGE 컴맨드로 WSAIoctl 함수가 호출될 때 포스팅 됩니다.

FD_ADDRESS_LIST_CHANGE 메시지는 SIO_ADDRESS_LIST_CHANGE 컴맨드로 WSAIoctl 함수가 호출될 때 포스팅 됩니다.

만약 어플리케이션이 WSAAsyncSelect함수를 호출하거나 재활성(re-enabling)함수를 호출했을 때 이미 이벤트가 발생되었다면, 이때의 메시지는 적절히 포스팅 됩니다. 이해를 쉽게 하기위해 아래의 일어날 수 순서 상황을 생각해 보세요.

1. 어플리케이션이 생성한 소켓에 대해서 listen 함수를 호출 했습니다.

2. 접속 요구가 들어왔으나 아직 accept 하지 않았습니다.

3. 어플리케이션은 이 소켓에 대해서 FD_ACCEPT로 WSAAsyncSelect 함수를 호출하였습니다. 이때 이벤트의 영속성이 보존되기 때문에 윈속은 FD_ACCEPT 메시지를 바로 포스팅 하게 됩니다.

FD_WRITE 이벤트는 약간 다르게 운용됩니다. 즉, 소켓이 connect/WSAConnect 함수의 호출로 첨 접속되었을 때, 또는 accept/WSAAccept 함수로 접속허용되었을 때, 또는, send 연산이 WSAEWOULDBLOCK 으로 실패(비동기소켓은 항상 WSAEWOULDBLOCK입니다)한 상태에서 전송용 버퍼공간이 사용 가능해졌을 때 FD_WRITE 메시지는 포스팅 됩니다. 따라서 어플리케이션은 FD_WRITE 메시지를 받았을 때 데이터의 전송이 가능하다고 판단 할 수 있습니다.

FD_OOB 이벤트는 소켓이 out-of-band 데이터를 부분적으로 수신하여 나열할 때만 사용됩니다. 만약 소켓이 out-of-band 데이터를 인라인 방식으로 수신하도록 나열 한다면, out-of-band데이터는 일반 데이터와 같이 다루어 지게 됩니다. 이때, 어플리케이션은 interest(?)를 등록해야 합니다. 일반데이터와 같이 다루어 진다는 의미는 뭔지 아시겠죠? FD_OOB 이벤트가 아닌 FD_READ이벤트가 발생했을 때 데이터를 수신한다는 의미입니다. 어플리케이션은 out-of-band 데이터가 SO_OOBINLINE 옵션으로 setsockopt함수나 getsockopt함수를 사용하여 핸들링 하는 방법을 설정할 수 있습니다.

FD_CLOSE 메시지의 에러코드는 소켓이 우아한닫힘(graceful close)인지 그렇지 않은 닫힘인지를 나타냅니다. 만약 에러코드가 0이라면, 소켓은 우아한닫힘 이라는 의미이고, 에러코드가 WSAECONNRESET 이라면, 소켓의 가상 회선망이 리셋되었다는 것을 의미합니다. 이러한 것들은 SOCK_STREAM과 같은 접속 지향형 소켓일 경우에만 사용될 수 있는 것들입니다.

FD_CLOSE 메시지는 소켓과 연관된 가상 회선망이 끊겼다는 것을 수신받았을 때 포스팅 됩니다. TCP 의 경우는 접속이 TIME WAIT 나 CLOSE WAIT 상태로 들어갔을 때 FD_CLOSE 메시지가 포스팅 되게 됩니다. 즉, 원격 종단점에서의 전송영역의 셧다운이나 closesocket 함수의 호출의 결과로 일어날 수 있는 메시지입니다. FD_CLOSE 메시지는 모든 데이터가 소켓으로부터 읽혀지고 난 후에 포스팅 됩니다 그렇긴 해도, 최대한 손실된 데이터들을 피하고자 한다면, FD_CLOSE 메시지를 받은 후에 남아있는 데이터들을 체크하는 것이 좋겠죠?

 

Return Values

지정된 네트웍 이벤트와 이벤트 객체가 성공적으로 설정되었으면, 함수는 0을 반환 합니다. 에러가 발생한 경우 SOCKET_ERROR을 리턴하고, WSAGetLastError 함수를 호출하여 특정한 에러코드를 얻어낼 수 있습니다.

 

Error Codes

WSANOTINITIALISED

이 함수를 사용하기 이전에 WSAStartup 함수를 성공적으로 호출해야 합니다.

WSAENETDOWN

네트웍 서브 시스템에서 에러가 났습니다.

WSAEINVAL 함수의 매개변수가 잘못 사용되었습니다.
WSAEINPROGRESS

블럭킹 윈속 v1.1이 진행상태에 있거나, 서비스 프로바이더가 아직 콜백함수를 진행하고 있습니다.

WSAENOTSOCK 지정된 소켓기술자가 소켓이 아닙니다.

 

QuickInfo

Windows NT : 사용가능
Windows : 사용가능
Windows CE : 지원되지 않음
Header :
          Win16/32 : winsock.h
          Win32-II : winsock2.h
Import Library :
          Win16 : winsock.lib
          Win32 : wsock32.lib
          Win32-II : ws2_32.lib

See Also

overview, WSAAsyncSelect, WSACloseEvent, WSACreateEvent, WSAEnumNetworkEvents, WSAWaitForMultipleEvents