|
예광탄 프로그램 |
문서번호 : 2011-CY001 |
조 : 4조 | |
작성자 : 유미영 | |
작성일 : 2011. 1.24 | |
제목 : WSAAsyncSelect 이용한 채팅 서버 &클라이언트 (개념파악 및 소스분석 ) |
개념파악 |
■ WSAAsyncSelect() 모델 |
- WSAAstnceSelect()함수가 핵심적인 역할을 함.
- 윈도우 메시지를 통하여 비동기적으로 소켓을 활용할 수 있음.
- 윈도우 메시지 형태로 소켓과 관련된 네트워크 이벤트를 처리할 경우,
☞ 멀티스레드를 사용하지 않고도 여러 개의 소켓을 처리 가능
장점 |
단점 |
- 소켓 이벤트를 윈도우 메시지 형태로 처리하므로, GUI 애플리케이션과 잘 결합 할 수 있다.
|
- 하나의 윈도우 프로시저에서 일반 윈도우 메시지와 소켓 메시지를 처리해야 하므로 성능저하의 요인이 된다. - 윈도우가 없는 콘솔 프로그램 등은 사용할 수 없다. |
n WSAAsyncSelect() 모델동작원리 |
|
n WSAAsyncSelect 모델을 이용한 소켓 입출력 절차 |
① WSAAsyncSelect() 함수를 이용하여 소켓을 위한 윈도우 메시지와 처리할 네트워크 이벤트를 등록.
아래 예제 참조
② 등록한 네트워크 이벤트가 발생하면 윈도우 메시지가 발생하고 윈도우 프로시저가 호출.
③ 윈도우 프로시저에서는 받은 메시지 종류에 따라 적절한 소켓 함수를 호출하여 처리.
A : 처리하고자 하는 소켓 B : 윈도우 핸들 (메세지를 받음)
C : 윈도우가 받을 메시지 ( 사용자정의 메시지) D : 처리할 네트워크 이벤트 조합(비트마스크조합)
예: 데이터 송수신이 가능한 상황이 될 때, 특정 윈도우 메시지를 발생시킴
#define MWM_SOCKET(WM_USER +1) //WM_USER + 1에 MWM_SOCKET 사용자 정의
메시지를 선언해줌
WSAAsyncSelect(sock, hWnd, MWM_SOCKET, FD_READ|FD_WRITE);
▦ 네트워크 이벤트
네트워크 이벤트 |
의미 |
FD_ACCEPT |
클라이언트가 접속하면 윈도우 메세지 발생시킴 |
FD_READ |
데이터 수신이 가능하면 윈도우 메세지 발생시킴 |
FD_WRITE |
데이터 송신이 가능하면 윈도우 메세지 발생시킴 |
FD_CLOSE |
상대가 접속을 종료하면 윈도우 메세지 발생시킴 |
FD_CONNECT |
접속이 완료되면 윈도우 메세지 발생시킴 |
FD_OOB |
OOB 데이터가 도착하면 윈도우 메시지 발생시킴 |
주요 코드 설명 – 서버 |
윈도우 프로시저
BOOL CALLBACK DlgProc(HWND hDlg,UINT iMessage,WPARAM wParam,LPARAM lParam) { switch(iMessage) { case WM_INITDIALOG: OnInitDlg(hDlg); return TRUE;
case WM_COMMAND: OnCommand(hDlg,LOWORD(wParam),HIWORD(wParam),(HWND)lParam); return TRUE;
case MWM_SOCK: OnSocket(hDlg,(SOCKET)wParam,LOWORD(lParam),HIWORD(lParam)); return TRUE; } return FALSE; } . . .
void OnInitDlg(HWND hDlg) { WSADATA wsadata; WSAStartup(MAKEWORD(2,2),&wsadata);
InitializeCriticalSection(&cs); SOCKET sock; sock = socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN servaddr={0,};
servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("192.168.34.7"); servaddr.sin_port = htons(PNUM);
int re = bind(sock,(SOCKADDR *)&servaddr,sizeof(servaddr)); re = listen(sock,5);
WSAAsyncSelect(sock,hDlg,MWM_SOCK,FD_ACCEPT|FD_CLOSE); }
// 서버는 클라이언트(상대)의 접속을 받아야 하므로 Accept이 있어야함
void On_Socket(HWND hWnd, SOCKET sock, WORD eid, WORD err) { switch(eid) { case FD_CLOSE: //상대방이 접속을 종료하면 On_Close(hWnd, sock, eid, err); break; case FD_ACCEPT: //상대방의 접속이 완료되면 On_Accept(hWnd,sock, eid, err); break; case FD_READ: //데이터 수신이 가능하면 On_Recv(hWnd, sock, eid, err); break; } } |
★ WSAAsyncSelect() 사용시 유의할 점 |
n WSAAsyncSelect() 함수를 호출하면 해당 소켓은 자동으로 non-Blocking 모드로 전환됨 ☞ 블로킹 소켓은 윈도우 메시지 루프를 정지시킬 수 있음
n Accept()함수가 리턴하는 소켓은 연결 대기 소켓과 동일한 속성을 지님 → 연결 대기 소켓은 직접 데이터 송수신을 하지 않으므로 FD_READ, FD_WRITE 이벤트를 처리하지 않음 그러나 accept() 함수가 리턴하는 소켓은 이벤트 처리를 해야하기 때문에 WSAAsyncSelect() 함수를 다시 호출하여 속성 변경이 필요함 ☻ n 윈도우 메시지를 받았을 때 적절한 소켓 함수를 호출하지 않으면, 다음 번에는 같은 윈도우 메시지가 발생하지 않음 예) 아래에 OnRecv() 참조.
|
☻
void OnAccept(HWND hDlg,SOCKET sock,WORD eid,WORD err) { SOCKADDR_IN clientaddr={0,}; int len = sizeof(clientaddr); SOCKET dosock; dosock = accept(sock,(SOCKADDR *)&clientaddr,&len); //accept으로 연결을 수락 했다면, sock을 받아옴.
li.push_back(dosock); SendMessage(GetDlgItem(hDlg,IDL_INFO),LB_ADDSTRING,0,(LPARAM)"접속"); WSAAsyncSelect(dosock,hDlg,MWM_SOCK,FD_READ|FD_CLOSE); } ① //이벤트 처리를 해야하기 때문에 다시 호출하여 속성 변경을 해줌
|
① WSAAsyncSelect(dosock,hDlg,MWM_SOCK,FD_READ|FD_CLOSE);
WSAAsyncSelect()를 사용함으로해서, 멀티스레드를 사용하지 않아도 되는 것을 보여준다.
이 함수는 accept으로 연결이 수락된 것이 확인되면 dosock(새로 생성한 소켓) 데이터를 수신할 수 있고, 상대방이 접속 종료를 하게 되면 윈도우 메시지가 발생되게 해준다.
void OnRecv(HWND hDlg,SOCKET sock,WORD eid,WORD err) {
char buf[1024]; if(recv(sock,buf,1000,0)) { SendMessage(GetDlgItem(hDlg,IDL_INFO),LB_ADDSTRING,0,(LPARAM)buf); Iter seek = li.begin(); while(seek!=li.end()) { send((*seek),buf,1000,0); seek++; } } }
클라이언트가 보내는 대화를 받아서 리스트박스에 추가하는 부분. |
void OnClose(HWND hDlg,SOCKET sock,WORD eid,WORD err) { ULONG ulRecv; if(ioctlsocket(sock, FIONREAD, &ulRecv) == 0) //두번째 인자를 FIONREAD로 주고, 세번째 인자에 ULONG타입인 변수의 주소값을 주면 세번째 인자로 넘긴 주소에 아직 처리되지 않은 수신 버퍼에 있는 데이터의 사이즈를 알 수 있음. (ioctlsocket함수 안의 매개변수에 대한 자세한 설명은 표 바로 아래 참조.) { if(ulRecv!=0) { PostMessage(hDlg,MWM_SOCK,(WPARAM)sock,eid); } else { closesocket(sock); SendMessage(GetDlgItem(hDlg,IDL_INFO),LB_ADDSTRING,0,(LPARAM)"접속종료"); Iter seek = find(li.begin(),li.end(),sock); if(seek != li.end()) { li.erase(seek); } } } } |
※ Ioctlsocket()함수란?
소켓의 입출력 모드를 제어하는 함수 |
A : [Input] 작업대상 소켓의 기술자를 명시함.
B : [Input] 소켓이 수행할 커맨드(명령어)
C : [Input/Output] command에 대한 입/출력 파라미터로 사용됨.
#.Command로 사용되는 방법
FIONBIO |
argp 매개변수는 unsigned long 값을 포인트 함. 소켓이 0일 때, 비동기모드 활성화 소켓이 0이 아닐 경우, 비활성화 |
FIONREAD |
네트워크 입력 버퍼에서 기다리고 있는 소켓으로부터 읽을 수 있는 데이터의 크기를 얻어내는데 사용 예 : 연결지향형 소켓(SOCK_STREAM)일 경우 FIONREAD 커멘드에 의한 ioctlsocket함수의 호출은 recv함수의 호출로 읽을 수 있는 데이터의 크기를 반환하게 됨. 메시지 지향형(예:SOCK_DGRAM)일 경우 FIONREAD 커멘드는 소켓에 큐 된 첫 번째 데이터그램의 크기를 반환함 |
SIOCATMARK |
소켓으로부터 out-of-band 데이터가 모두 읽혀졌는지 판단 읽혀지기 원하는 out-of-band 데이터가 없을 경우 TRUE반환 그렇지 않은 경우 FALSE 반환 |
주요 코드 설명 - 클라이언트 |
- 윈도우 프로시저는 서버와 같다.
void OnCommand(HWND hDlg,WORD cid,WORD cmsg,HWND cWnd) { switch(cid) { case IDCANCEL: CancelProc(hDlg); break; case IDB_SEND: SendProc(hDlg); break; } } void SendProc(HWND hDlg) { char buf[1024]; GetDlgItemText(hDlg,IDE_MSG,buf,1000); send(sock,buf,1000,0); //edit창에 쓴 메시지(buf)를 sock에 보내줌 } |
void OnSocket(HWND hDlg,SOCKET sock,WORD eid,WORD err) { switch(eid) { case FD_CLOSE: OnClose(hDlg,sock,eid,err); break; case FD_READ: OnRecv(hDlg,sock,eid,err); break; } } |
void OnRecv(HWND hDlg,SOCKET sock,WORD eid,WORD err) { char buf[1024]; if(recv(sock,buf,1000,0)) { SendMessage(GetDlgItem(hDlg,IDL_MSG),LB_ADDSTRING,0,(LPARAM)buf); } } //대화창(리스트박스)에 대화를 보내줌. |
void OnClose(HWND hDlg,SOCKET sock,WORD eid,WORD err) { ULONG ulRecv; if(ioctlsocket(sock, FIONREAD, &ulRecv) == 0) { if(ulRecv!=0) { PostMessage(hDlg,MWM_SOCK,(WPARAM)sock,eid); } else { closesocket(sock); } } } |
실행화면 |
|