IT_Programming/Network Programming

[펌] 소켓 입출력 모델 - WSAEventSelect 모델

JJun ™ 2009. 11. 18. 13:01

------------------------------------------------------------------------------------

출처: http://blog.naver.com/ree31206/46430181

------------------------------------------------------------------------------------

 

소켓 입출력 모델 - WSAEventSelect 모델

 

- WSAEventSelect() 함수가 핵심적인 역활을 한다

- 이벤트 객체를 통해 네트워크 이벤트를 감지한다

- 각 소켓에 대해 이벤트 객체를 생성하고 이 이벤트 객체를 관찰함으로써 멀티 쓰레드를 사용하지

   않고도 여러개의 소켓을 처리할수 있다

 

 

[WSAEventSelect 모델을 이용한 소켓 입출력 절차]

 

1. 소켓을 생성할때마다 WSACreateEvent() 함수를 이용하여 이벤트 객체를 생성한다

 

2. WSAEventSelect() 함수를 이용하여 소켓과 이벤트 객체를 짝지음과 동시에,

   처리할 네트워크 이벤트를 등록한다. 예를 들면 소켓을 통해 데이터를 보내거나 받을수 있는

   상황이 되면 이벤트 객체를 신호상태로 변경하라는 내용을 등록.

 

3. WSAWaitForMulipleEvents() 함수를 호출하여 이벤트 객체가 신호상태가 되기를 기다린다.

    등록한 네트워크 이벤트가 발생하면 해당 소켓과 연관된 이벤트 객체가 신호상태가 된다.

 

4. WSAEnumNetworkEvents() 함수를 호출하여 발생한 네트워크 이벤트를 알아내고,

   적절한 소켓 함수를 호출하여 처리.

 

 

 

[이벤트 객체 생성과 제거]

 

WSACreateEvent() 함수는 이벤트 객체를 생성하는 역활을 한다.

생성되는 이벤트 객체는 항상 수동 리셋(manual - reset) 이벤트며 비신호 상태로 시작된다.

사용이 끝난 이벤트 객체는 WSACloseEvent() 함수를 호출해서 제거한다.

 

WSAEVENT WSACreateEvent();                  // 성공 : 이벤트 객체 핸들.                        

                                                                  // 실패 : WSA_INVALID_EVENT

BOOL WSACloseEvent(WSAEVENT hEvent);   // 성공 : TRUE.   실패 : FALSE

 

 

 

[소켓과 이벤트 객체 짝짓기]

WSAEventSelect() 함수는 소켓과 이벤트 객체를 짝지음과 동시에 처리할 네트워크 이벤트를

등록하는 역활을 한다

 

int WSAEventSelect(

    SOCKET s,                                  // 처리하고자 하는 소켓

    WSAEVENT hEventObject,           // 소켓과 연관시킬 이벤트 객체의 핸들값

    long INetWorkEvents                   // 처리할 네트워크 이벤트 종류를 마스크 조합으로 나타냄.

                                                      // WSAAsyncSelect 모델과 동일

);

 

WSAEVENT hEvent = WSACreateEvent();

WSAEventSelect(s, hEvent, FD_READ | FD_WRITE);

 

 

[이벤트 객체의 신호상태 감지하기]

 

DWORD WSAWaitForMultipleEvents(

    DWORD cEvents,

    const WSAEVENT* lphEvents,

    BOOL fWaitAll,

    DWORD dwTimeout,

    BOOL fAlertable

);

성공 : WSA_WAIT_EVENT_0 ~ WSA_WAIT_EVENT_0 + cEvents-1 또는 WSA_WAIT_TIMEOUT.

실패 : WSA_WAIT_FAILED

 

- cEvents, lphEvents : 이벤트 핸들을 모두 배열에 저장해야 한다. cEvents 는 배열 원소 개수.

                                   lphEvents 는 배열의 시작주소

 

- fWaitAll : TRUE 이면 모든 이벤트 객체가 신호상태가 될때까지 대기. FALSE 이면 이벤트 객체가

                 신호상태가 되는 즉시 리턴

 

- dwTimeout : 이 시간 내에 조건이 만족하지 않더라도 함수는 리턴한다. 

                       대기시간으로 WSA_INFINTE 값을 사용하면 조건이 만족할때까지 무한 대기

 

- fAertable : 입출력 완류 루틴과 관련된 부분. WSAEventSelect 모델에서는 항상 FALSE

 

 

 

[구체적인 네트워크 이벤트 알아내기]

 

int WSAEnumNetworkEvents(

    SOCKET s,                          // 대상 소켓

    WSAEVENT hEventObject,   // 대상 소켓 s 와 짝지어둔 이벤트 객체 핸들을 넘기면 자동으로

                                               // 비신호 상태가 됨

    LPWSANETWROKEVENTS lpNetworkEvents   //  발생한 네트워크 이벤트와 오류정보가

                                                                         // 이 변수에 저장

);   성공 : 0.   실패 : SOCKET_ERROR

 

typedef struct _WSANETWORKEVENTS{

    long INetworkEvents;

    int iErrorCode[FD_MAX_EVENTS];

} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;

 

 

* iErrorCode[] 를 참조하기 위한 배열 인덱스 값

FD_ACCEPT : 대응함수 FD_ACCEPT_BIT

FD_READ : 대응함수 FD_READ_BIT

FD_WRITE : 대응함수 FD_WRITE_BIT

FD_CLOSE : 대응함수 FD_CLOSE_BIT

FD_CONNECT : 대응함수 FD_CONNECT_BIT

FD_OOB : 대응함수 FD_OOB_BIT

 

 

 

[WSAEventSelect 모델을 이용한 에코 서버]

 

계속 보왔던 구조의 형태이기 때문에 분석은 Pass ~~

 

#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>

 

#define BUFSIZE 512

 

// 소켓 정보 저장을 위한 구조체
struct SOCKETINFO
{
    SOCKET sock;
    char     buf[BUFSIZE+1];
    int        recvbytes;
    int        sendbytes;
};

 

int nTotalSockets = 0;
SOCKETINFO *SocketInfoArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];

 

// 소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock);
void RemoveSocketInfo(int nIndex);
// 오류 출력 함수
void err_quit(char *msg);
void err_display(char *msg);
void err_display(int errcode);

 

int main(int argc, char* argv[])
{
    int retval;
 
    // 윈속 초기화
    WSADATA wsa;
    if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)
        return -1;
    SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock == INVALID_SOCKET) err_quit("socket()");
 

    // 소켓 정보 추가
    if(AddSocketInfo(listen_sock) == FALSE)
        return -1;
 
    // WSAEventSelect()
    retval = WSAEventSelect(listen_sock, EventArray[nTotalSockets-1], 

                                               FD_ACCEPT|FD_CLOSE);
    if(retval == SOCKET_ERROR) err_quit("WSAEventSelect()");
 
    SOCKADDR_IN serveraddr;
    ZeroMemory(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(9000);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    retval = bind(listen_sock, (SOCKADDR *)&serveraddr, sizeof(serveraddr));
    if(retval == SOCKET_ERROR) err_quit("bind()");
 
    retval = listen(listen_sock, SOMAXCONN);
    if(retval == SOCKET_ERROR) err_quit("listen()");
 
    // 데이터 통신에 사용할 변수
    int index;
    WSANETWORKEVENTS NetworkEvents;
    SOCKET client_sock;
    SOCKADDR_IN clientaddr;
    int addrlen;
 
    while(1)
    {
        // 이벤트 객체 관찰
        index = WSAWaitForMultipleEvents(nTotalSockets, EventArray, FALSE, WSA_INFINITE,

                                                                FALSE);
        if(index == WSA_WAIT_FAILED) {
            err_display("WSAWaitForMultipleEvents()");
            continue;
        }

        index -= WSA_WAIT_EVENT_0;
  
        // 구체적인 네트워크 이벤트 알아내기
        retval = WSAEnumNetworkEvents(SocketInfoArray[index]->sock, EventArray[index],

                                                               &NetworkEvents);

        if(retval == SOCKET_ERROR) {
            err_display("WSAEnumNetworkEvents()");
            continue;
        }
  
        // FD_ACCEPT 이벤트 처리
        if(NetworkEvents.lNetworkEvents & FD_ACCEPT) {
            if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
                err_display(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
                continue;
            }
   
            addrlen = sizeof(clientaddr);
            client_sock = accept(SocketInfoArray[index]->sock, (SOCKADDR *)&clientaddr,

                                                  &addrlen);


            if(client_sock == INVALID_SOCKET) {
                err_display("accept()");
                continue;
            }
            printf("[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n", 

                                      inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
   
            if(nTotalSockets >= WSA_MAXIMUM_WAIT_EVENTS) {
                printf("[오류] 더 이상 접속을 받아들일 수 없습니다!\n");
                closesocket(client_sock);
                continue;
            }
   
            if(AddSocketInfo(client_sock) == FALSE)
                continue;
   
            retval = WSAEventSelect(client_sock, EventArray[nTotalSockets-1], 

                                                        FD_READ|FD_WRITE|FD_CLOSE);


            if(retval == SOCKET_ERROR)

                   err_quit("WSAEventSelect()");
        }
  
        // FD_READ, FD_WRITE 이벤트 처리
        if(NetworkEvents.lNetworkEvents & FD_READ || NetworkEvents.lNetworkEvents &

                                                                                                                    FD_WRITE) {
            if(NetworkEvents.lNetworkEvents & FD_READ && NetworkEvents.iErrorCode

                                                                                                     [FD_READ_BIT] != 0) {
               err_display(NetworkEvents.iErrorCode[FD_READ_BIT]);
               continue;
            }


            if(NetworkEvents.lNetworkEvents & FD_WRITE && NetworkEvents.iErrorCode

                                                                                                   [FD_WRITE_BIT] != 0) {
                err_display(NetworkEvents.iErrorCode[FD_WRITE_BIT]);
                continue;
            }


            SOCKETINFO *ptr = SocketInfoArray[index];
    

            if(ptr->recvbytes == 0) {
                // 데이터 받기
                retval = recv(ptr->sock, ptr->buf, BUFSIZE, 0);
                if(retval == SOCKET_ERROR) {
                    if(WSAGetLastError() != WSAEWOULDBLOCK) {
                        err_display("recv()");
                        RemoveSocketInfo(index);
                    }
                    continue;
                }
                ptr->recvbytes = retval;
                // 받은 데이터 출력
                ptr->buf[retval] = '\0';
                addrlen = sizeof(clientaddr);
                getpeername(ptr->sock, (SOCKADDR *)&clientaddr, &addrlen);
                printf("[TCP/%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr),

                                                                           ntohs(clientaddr.sin_port), ptr->buf);
            }
   
            if(ptr->recvbytes > ptr->sendbytes) {
                // 데이터 보내기
                retval = send(ptr->sock, ptr->buf + ptr->sendbytes,
                ptr->recvbytes - ptr->sendbytes, 0);
                if(retval == SOCKET_ERROR) {
                    if(WSAGetLastError() != WSAEWOULDBLOCK) {
                        err_display("send()");
                        RemoveSocketInfo(index);
                    }
                    continue;
                }
                ptr->sendbytes += retval;
                // 받은 데이터를 모두 보냈는지 체크
                if(ptr->recvbytes == ptr->sendbytes)
                    ptr->recvbytes = ptr->sendbytes = 0;
            }
        }
  
        // FD_CLOSE 이벤트 처리
        if(NetworkEvents.lNetworkEvents & FD_CLOSE) {
            if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
                err_display(NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
           

            RemoveSocketInfo(index);
        }
    }
    WSACleanup();
    return 0;
}

 

// 소켓 정보 추가

void AddSocketInfo(SOCKET sock)
{
    if(nTotalSockets >= WSA_MAXIMUM_WAIT_EVENTS) {
        printf("[오류] 소켓 정보를 추가할 수 없습니다!\n");
        return FALSE;
    }
 
    SOCKETINFO *ptr = new SOCKETINFO;
    if(ptr == NULL) {
        printf("[오류] 메모리가 부족합니다!\n");
        return FALSE;
    }
 
    WSAEVENT hEvent = WSACreateEvent();
    if(hEvent == WSA_INVALID_EVENT) {
        err_display("WSACreateEvent()");
        return FALSE;
    }
 
    ptr->sock = sock;
    ptr->recvbytes = 0;
    ptr->sendbytes = 0;
    SocketInfoArray[nTotalSockets] = ptr;
    EventArray[nTotalSockets] = hEvent;
    nTotalSockets++;
    return TRUE;
}

 

// 소켓 정보 삭제
void RemoveSocketInfo(int nIndex)
{
    SOCKETINFO *ptr = SocketInfoArray[nIndex];
 
    // 클라이언트 정보 얻기
    SOCKADDR_IN clientaddr;
    int addrlen = sizeof(clientaddr);
    getpeername(ptr->sock, (SOCKADDR *)&clientaddr, &addrlen);
    printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n", 

                                             inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
 
    closesocket(ptr->sock);
    delete ptr;
    WSACloseEvent(EventArray[nIndex]);
 
    for(int i=nIndex; i<nTotalSockets; i++)

    {
        SocketInfoArray[i] = SocketInfoArray[i+1];
        EventArray[i] = EventArray[i+1];
    }
    nTotalSockets--;
}

 

// 소켓 함수 오류 출력 후 종료
void err_quit(char *msg)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER|
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
        MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR);
    LocalFree(lpMsgBuf);
    exit(-1);
}


// 소켓 함수 오류 출력
void err_display(char *msg)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER|
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, WSAGetLastError(),
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf);
    LocalFree(lpMsgBuf);
}

 

// 소켓 함수 오류 출력
void err_display(int errcode)
{
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER|
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, errcode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    printf("[오류] %s", (LPCTSTR)lpMsgBuf);
    LocalFree(lpMsgBuf);
}