안녕하세요?
이번 장에서는 '비동기소켓'에 대해 알아볼까요?. 비동기 소켓이란 말이 생소하실 텐데 쉽게 말해서 메시지방식의(이벤트) 소켓을 뜻하는 말입니다.
Win32에선 DOS나 UNIX와는 달리 이벤트성 메시지를 통해 프로그램이 제어되죠? 마찬가지예요.
일반 소켓함수는 대부분 UNIX와 같은 순차적으로 실행이 되지만 비동기소켓은 윈도우와 같이 메시지방식으로 진행된다고 생각하시면 됩니다.
우리 저번에 블록함수에 대해서 잠깐 알아봤죠? 다시한번 개념을 정리해 드리자면..
'블록함수란 특정이벤트가 발생하기 전까지 리턴하지 않는 함수'
라고 정의할 수 있겠네요. 아래는 블록함수의 주요 목록들 입니다.
accept, connect, send, recv
이외에도 몇가지 더 있지만 그건 레퍼런스를 참고하시고 저 4개의 함수가 블록된다는 점을 주의하셔야 해요. accept는 서버용 소켓함수인데 호출하면 클라이언트의 connect요청이 올때까지 리턴되지 않고 계속 '블록상태'에 놓이게 되는거죠. 이해가 안되신다면 아래 코드를 보세요.
clntSock = accept( ... );
MessageBox( hWnd, "accept 함수가 리턴됨", "알림", MB_OK );
accept함수가 호출이되면 accept함수가 리턴된 후에 MessageBox가 띄워지겠죠?
그렇지만 만약 accpet를 호출한 뒤에 클라이언트로부터 connect요청이 없다면 connect 요청을 받을때까지 무한정 대기하게 되죠. 이를 바로 '블록함수'라고 합니다.
connect함수도 블록함수인데 클라이언트측에서 connect함수를 호출하고 서버측에서 accept를 하지 않는다면 블록상태에 놓이게 됩니다.
send와 recv도 마찬가지로 데이터를 보내서 받지않을때.. 데이터를 받을준비를 하고있는데 데이터를 보내오지 않을때 블록상태에 놓이게 되겠죠?
이를 해결하기위해 '비동기소켓'이란걸 만들게 되었는데 블록소켓을 비동기 소켓으로 전환하게 되면 모든 블록함수에 대해 블록되지 않고 곧바로 리턴하게 됩니다. 그 결과가 성공이든 실패든 무조건 리턴하게 되죠. 그런뒤에 어떤 특정이벤트가 일어날때 해당 윈도우로 메시지를 보내게 됩니다.
예를 들어보자면 서버측에서 비동기소켓을 만들어놓고 accept 함수를 호출했다고 합시다. 이때 클라이언트의 접속요청이 없었다면 블록상태에 놓이게 되겠지만 비동기 소켓이니 블록되지 않고 실패를 리턴합니다.
그리고 다른 메시지를 처리하다가 클라이언트로부터 connect 요청이 발생되면 그때 서버측 윈도우로 FD_ACCEPT 라는 비동기소켓 메시지를 보내줍니다. 이 메시지는 '클라이언트로부터 접속요청이 들어왔으니 accept 함수를 호출해서 접속요청을 수락하라' 라는 뜻이죠. 이때 서버측에서는 다시한번 accept를 호출해서 클라이언트의 접속요청을 받아들이면 되겠죠. 물론 이때도 바로 리턴을 하지만 클라이언트의 접속 요청이 있던 상태이기 때문에 성공적으로 클라이언트의 소켓이 리턴되겠죠.
이런 비동기소켓을 생성하는 함수가 WSAAsyncSelect 함수입니다.
int WSAAsyncSelect( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );
s : 비동기모드로 전환할 소켓
hWnd : 비동기메시지를 받을 윈도우의 핸들
wMsg : 메시지를 정의
lEvent : hWnd가 받을 비동기 메시지. 다음의 값들을 | 연산자로 묶어 대입해준다.
FD_ACCEPT : 클라이언트의 연결요청이 있을때 일어나는 메시지
FD_CONNECT : 서버가 연결요청을 수락했을때(서버에 접속했을때)일어나는 메시지.
FD_READ : 소켓에 읽어들일 데이터가 있을때 일어나는 메시지.
FD_WRITE : 소켓에 버퍼가 비었을때, send를 호출할 수 있을때 일어나는 메시지.
FD_CLOSE : 해당소켓이 연결을 해제하였을때 일어나는 메시지.
... 그 외의 메시지는 레퍼런스를 참고
리턴값은 성공적으로 비동기소켓으로 변환했을때 0을, 실패했을때는 SOCKET_ERROR를 리턴하며 자세한 에러정보는 WSAGetLastError호출로 알아볼 수 있습니다.
wMsg 인수는 메시지를 정의하는데 대게 사용자 정의메시지를 대입해주면 됩니다. 다음과 같은 형식으로 말이죠.
#define WM_USERASYNC (WM_USER+1)
...
WSAAsyncSelect( ..., ..., WM_USERASYNC, ... );
그리고 비동기소켓으로 전환한뒤에 다시 블록소켓으로 전환시킬때는 3번째와 4번째 인수를 0으로 대입한뒤에 호출해주면 됩니다. 그러면 비동기 메시지를 받지 않게 되죠.
비동기 메시지가 발생되면 wParam에는 해당 이벤트를 발생시킨 소켓의 핸들이, lParam의 하위워드에는 에러정보, lParam의 상위워드에는 비동기이벤트의 정보가 들어있는데 이를 코드의 이식성을 위해 winsock2.h 에 따로 매크로로 정의하고 있습니다.
#define WSAGETSELECTERROR(lParam) HIWORD(lParam)
#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)
MSDN문서에 따르면 비동기 메시지는 항상 메시지를 해당 윈도우로 보내주지 않는다고 합니다. 쉽게 풀어서 설명하자면.. 비동기 소켓에 데이터가 도착하게 되면 운영체제 내부에선 FD_READ메시지를 해당 윈도우로 보내게 됩니다. 이때 이 메시지는 아직 해당 윈도우에 메시지는 도착하지 않은상태이며 내부적으로 많은 메시지를 처리하는 상태라 메시지큐에 들어가 대기상태라고 가정해 보죠.
그런데 이때 또 다시 비동기 소켓에 데이터가 도착했다면 이때는 FD_READ 메시지를 해당 윈도우로 보내지 않는 다는 것이죠. 왜냐하면 이미 한번 보내버린 메시지, 즉.. 포스팅한 메시지에 대해서는 그 관리를 잃어버린다는 겁니다. 이미 FD_READ메시지를 한번 보내버렸다면 운영체제는 더이상 해당 소켓에 대해 FD_READ 메시지를 일으키도록 관리해주지 않는다는 것이죠. 이렇게 되면 결국엔 데이터는 2개가 도착했지만 FD_READ가 한번밖에 일어나질 않겠죠?
그렇다면 FD_READ 메시지가 다시 발생되도록 해주어야 할 필요가 있는데.. 비동기 메시지가 도착할때마다 WSAAsyncSelect를 다시 호출해서 비동기 메시지를 셋팅해줘야 할까요? 그렇지 않습니다. FD_READ메시지가 일어나면 운영체제는 해당소켓에 대해 더이상 FD_READ메시지에 대해서 신경을 쓰지 않게 되지만 해당 소켓에 대해 recv를 호출하게 되면 다시 FD_READ메시지가 활성화 되고 해당윈도우는 FD_READ 메시지를 또 받을 수 있게 됩니다.
이런 recv와 같은 기능을 하는 함수들을 're-enabling'함수라고 하는데 아래는 're-enabling'함수 목록들입니다.
FD_READ recv, recvfrom, WSARecv, WSARecvFrom
FD_WRITE send, sendto, WSASend, WSASendTo
FD_ACCEPT accept, WSAAccept
FD_CONNECT 없음(해당소켓에 대해 한번만 일어나는 메시지이기 때문)
FD_CLOSE 없음(해당소켓에 대해 한번만 일어나는 메시지이기 때문)
비동기 소켓에 대해서는 신경을 써줘야 할게 한두가지가 아닙니다. 그중에서 특히 신경을 써야하는게 WSAEWOULDBLOCK 인데 이 비동기소켓 에러메시지는 블록함수를 호출할때 대게 많이 발생합니다. 이 함수는 블록될 수 있다 라는걸 알려주는 코드죠. WSAGetLastError 호출시 얻어낼 수 있는 에러코드입니다.
비동기소켓은 어떤 블록함수가 성공적으로 일을 수행하지 않아도 바로 리턴되기 때문에 위의 에러가 자주 발생하게 됩니다. 물론 함수가 실패했다는 뜻은 아니며 다만 이것을 처리하는데 약간의 시간이 필요하다 라는걸 알려주는 것일 뿐입니다.
예를들어 어떤 클라이언트가 비동기소켓으로 전환한뒤에 connect 를 호출하면 접속하는데 시간이 조금 걸리는 상황이라면 SOCKET_ERROR 를 리턴하게 됩니다. 만약 이 리턴값을 에러로 처리해버리면 안됩니다. 반드시 WSAGetLastError로 값을 확인해보고 WSAEWOULDBLOCK 이라면 그냥 루프를 다시 돌려야 합니다. connect 함수같은 경우는 서버에 접속되기까지 회선에 따라 다르지만 약간의 시간이 걸리기 때문이죠.
다음 장에서는 직접 비동기소켓을 이용해서 서버를 구현해보도록 하죠.
기다려 주세요. 아듀~!
- Reference
'[ Windows Program ] > Windows API' 카테고리의 다른 글
멀티쓰레드 윈도우즈 소켓 프로그래밍 #1 (0) | 2011.03.08 |
---|---|
비동기소켓 서버구현 (0) | 2011.03.07 |
데이터 전/수송 함수인 send/recv (0) | 2011.03.07 |
클라이언트의 연결을 처리하는 accept 함수 (2) | 2011.03.07 |
클라이언트의 접속을 가능하게 하는 listen 함수 (0) | 2011.03.07 |