Skip to content

AcceptEx DisconnectEx ConnectEx

ozt88 edited this page Apr 12, 2015 · 4 revisions

좀더 Proactive하게!

Proactor와 Reactor의 차이점을 알고나니, 지금까지 사용한 IOCP 서버 구조의 이상한 점을 발견할 수 있었다. WSASend, WSARecv하는 부분은 분명 Proactive한 I/O 작업을 수행하는데, accept하는 main(listen) thread는 왜 while문 하나 끼고 블로킹한 루프를 도는 시대착오적 방식으로 accept를 수행하는가? proactive한 accept가 불가능한 것인가? 그렇게 생각해보면 proactive한 accept라는것이 어려울 것같다는 생각이 들기도 하지만, 검색해보니 이미 잘 만들어진 proactive accept인 AcceptEx가 존재하고 있었다.

AcceptEx

accept를 proactive한 방식으로 구현한 함수. 최신 운영체제 및 개발자 도구에서 지원한다. 미리 여러개의 소켓을 만들어 놓고(소켓 풀을 활용하면 좋겠다.), 여러개의 accept 명령을 비동기로 예약함. accept 가능한 연결이 발생하면 비동기 프로세스가 accept를 수행하고 완료된 결과를 complete handler에게 통지. 통지 받은 complete handler는 연결된 클라이언트에게 이후 작업을 처리하면 깔끔하게 모든걸 proactive하게 구성할 수 있다. 함수의 구성은 다음과 같다.

BOOL AcceptEx( 
   _In_ SOCKET sListenSocket, //listen 소켓
   _In_ SOCKET sAcceptSocket, //새로 연결될 소켓
   _In_ PVOID lpOutputBuffer, //연결시 받은 첫 데이터 블록 + 연결된 주소를 받을 버퍼
   _In_ DWORD dwReceiveDataLength, //연결시 받을 데이터(주소제외) 크기, 0이면 데이터 수신 안함
   _In_ DWORD dwLocalAddressLength, //로컬 주소 받는 버퍼 크기, 최소 16바이트 이상
   _In_ DWORD dwRemoteAddressLength, //원격 주소 받을 버퍼 크기, 최소 16바이트 이상
   _Out_ LPDWORD lpdwBytesReceived, //수신할 총 데이터의 바이트 단위 크기
   _In_ LPOVERLAPPED lpOverlapped //overlapped 구조체 포인터
);
//반환 값 : 에러시 FALSE, 성공시 TRUE 
//WSAGetLastError() : ERROR_IO_PENDING (작업 진행중), WSAECONNRESET(accept하기전에 종료됨)

단순히 연결만 해주는 accept 작업에 그렇게 많은 리소스가 들어가는가? 라고 반문할지도 모르겠지만, 한꺼번에 엄청난 접속을 요청하는 상황을 생각해보자. 인기 온라인 게임을 해본 사람이라면 한번쯤 겪어봤으리라. 한번 accept할때마다 메인 쓰레드가 socket을 생성하고, accept를 하느라 blocking되고, 그동안 다른 유저들은 계속 대기열에 있고... 아무리 짧은 시간이라도 동시에 처리해야 하는게 엄청나게 많아진다면 문제가 된다. (대기열이 터져버렷!)

이때 proactive한 AcceptEx를 사용한다면, socket 풀을 활용하여 작업을 진행하므로, socket을 할당하는 시간을 절약할 수 있고, accept작업도 최대한 효율적으로 서버의 자원을 활용하여 빠르게 유저를 접속시킬 것이다. 비슷한 이유에서 Connect/Disconnect도 proactive하게 작성할 필요가 생길 수 있다. 아래에 이 함수들의 구성을 추가로 설명한다.

ConnectEx

BOOL PASCAL ConnectEx( 
   _In_ SOCKET s, //연결할 소켓
   _In_ const struct sockaddr *name, //연결할 sockaddr구조체
   _In_ int namelen, //연결할 sockaddr 길이
   _In_opt_ PVOID lpSendBuffer, //연결 직후 전송된 데이터가 저장될 버퍼
   _In_ DWORD dwSendDataLength, //위 버퍼의 사이즈
   _Out_ LPDWORD lpdwBytesSent, //연결 후 전송된 전체 데이터 사이즈
   _In_ LPOVERLAPPED lpOverlapped //overlapped 더 이상의 설명은 생략한다
);
//반환 값 : error시 FALSE, 성공시 TRUE
//WSAGetLastError : ERROR_IO_PENDING(작업 진행중), 
//                  WSAECONNREFUSED, WSAENETUNREACH, WSAETIMEDOUT(다른 connect에서 같은 소켓 사용중)

DisconnectEx

BOOL DisconnectEx(
   _In_ SOCKET hSocket, //종료할 소켓
   _In_ LPOVERLAPPED lpOverlapped, //overlapped 구조체...
   _In_ DWORD dwFlags, //소켓 처리방법(TF_REUSE_SOCKET하면 재사용)
   _In_ DWORD reserved //0을 쓴다.
);
//반환 값 : error시 FALSE, 성공시 TRUE
//WSAGetLastError : WSAEFAULT, WSAEINVAL(lpOverlapped, dwFlags에 잘못된 인자)
//                  WSAENOTCONN (소켓 불량 : 연결안된 소켓, 이미 closing 중)

플래그로 TF_REUSE_SOCKET를 사용하면 종료된 소켓을 재사용할 수 있다. 이것을 활용하여 소켓 풀에서 소켓을 계속해서 할당 해제 하지 않아도 효율적으로 소켓들을 운용할 수 있다. disconnect 완료통지를 받으면 풀에 다시 집어넣어서 재사용하자.