IT_Programming/Network Programming

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

JJun ™ 2009. 11. 18. 13:20

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

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

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

 

소켓 입출력 모델 - Overlapped 모델

 

 

[동기 입출력(synchronous I/O)]

애플리캐이션은 입출력 함수를 호출한 후 입출력 작업이 끝날때 까지 대기하다 끝나면 입출력 결과를 처리하거나 다른 작업을 할수 있다. Select, WSAAsyncSelect, WSAEventSelect 소켓 모델은 모두 동기 입출력 방식으로 동작.  단, 입출력 함수를 안전하게 호출할 수 있는 시점을 운영체제가 알려주기 때문에 단순한 동기 입출력 방식보다 편하게 여러 소켓을 처리할수 있다. 이와 같이 운영체제가 함수 호출 시점을 알려주는 개념을 비동기 통지라고 부른다.

 

 

[비동기 입출력(asynchronous I/O = overlapped I/O)]

어플리캐이션은 입출력 함수를 호출한 후 입출력 작업의 완료 여부와 무관하게 다른 작업을 할 수

있다. 입출력 작업이 끝나면 운영체제는 작업 완료를 어플리캐이션에게 알려준다.

비동기 입출력 방식에서는 입출력 완료를 운영체제에서 알려주는 개념이 반드시 필요하므로

비동기 통지도 사용한다고 볼수 있다. Overlapped, Completion Port(IOCP)는 비동기 입출력과

비동기 통지를 결합한 형태라 할수 있다

 

 

[Overlapeed 모델]

 

* 사용 절차

 

1. 비동기 입출력을 지원하는 소켓을 생성

  - socket() 함수로 생성한 소켓은 기본적으로 비동기 입출력을 지원한다

2. 비동기 입출력을 지원하는 소켓 함수 호출(총 13개의 함수)

  - AcceptEx(), ConnectEx(), DisconnectEx(), TransmitFile(), TransmitPackets(), WSAIoctl(),

    WSASPIoctl(), WSAProviderConfigChange(), WSARecvMsg(), WSARecv(), WSARecvFrom(),

    WSASend(), WSASendTo()

 

3. 운영체제는 소켓 입출력 작업 완료를 애플리캐이션에게 알려주고(=비동기 통지),

    애플리케이션은 결과를 처리

 

 

 

* 비동기 통지 방식에 따른 Overlapped 모델 분류

 

1. Overlapped 모델( I )

: 소켓 입출력 작업이 완료되면 운영체제는 애플리케이션이 등록한 이벤트 객체를 신호상태로 바꾼다.

  애플리케이션은 이벤트 객체를 관찰함으로써 작업 완료를 감지할수 있다

 

2. Overlaped 모델( II )

: 소켓 입출력 작업이 완료되면 운영체제는 애플리케이션이 등록한 함수를 자동으로 호출한다.

  일반적으로 운영체제가 호출하는 애플리케이션 함수를 콜백 함수(callback function) 라 부르는데.

  특별히 Overlapped 모델에서는 완료 루틴(completion routine) 이라 칭한다

 

 

[WSASend(), WSARecv()]

int WSASend(

    SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,

    LPDWORD lpNumberOfBytesSent,

    DWORD dwFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoution

);    성공 : 0.   실패 : SOCKET_ERROR

 

int WSARecv(

    SOCKET s, LPWSABUF lpBUffers, DWORD dwBufferCount,

    LPDWORD lpNumberOfBytesRecvd,

    LPDWORD lpFlags,

    LPWSAOVERLAPPED lpOverlapped,

    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine

);    성공 : 0.    실패 : SOCKET_ERROR

 

s : 비동기 입출력을 할 소켓

lpBuffers : WSABUF 구조체 배열의 시작주소. 각각의 배열 원소(WSABUF 타입)는 버퍼의 시작 주소

                 와 길이(바이트단위)를 담고 있다.

 

dwBufferCount : WSABUF 구조체 배열의 원소개수.

lpNumberOfBytesSent, lpNumberOfBytesRecvd

 : DWORD 형 변수 주소값으로 함수 호출이 성공하면 이 변수에 보내거나 받은 바이트가 저장된다.

 

dwFlags, lpFlags : send(), recv() 함수의 마지막 인자와 동일한 역활

lpOverlapped : WSAOVERLAPPED 구조체 변수의 주소값.

                        이 구조체는 비동기 입출력을 위한 정보를 운영체제에 전달하거나 운영체제가

                        비동기 입출력 결과를 애플리케이션에 전달할때 사용.

                        WSAOVERLAPPED 구조체 변수중 처음 네 개는 운영체제가 내부적으로만 사용.

                        마지막 변수인 hEvent 는 이벤트 객체 핸들값으로 Overlapped 모델( I ) 에서만

                        사용한다. 입출력 작업이 완료되면 hEvent 가 가리키는 이벤트 객체는 신호상태가

                        된다.

 

lpCompletionRoutine :  입출력 작업이 완료되면 운영체제가 자동으로 호출할 완료루틴(콜백함수)의

                                    주소값

 

 

 

* WSASend(), WSARecv() 함수 특징

 

송신측에서 WSABUF 구조체를 사용하면 여려개의 버퍼에 저장된 데이터를 모아서(Gather) 보낼 수 있다. 수신측에서도 역시 WSABUF 구조체를 사용하면 여러개의 버퍼에 흩어(Scatter)저장할 수 있다.

 

buf[128];

buf[256];

WSABUF wsabuf[2];

wsabuf[0].buf = buf1;

wsabuf[0].len = 128;

wsabuf[1].buf = buf2;

wsabuf[1].len = 256;

 

// 송신측 코드

WSASend(sock, wsabuf, 2, ...);

 

// 수신측 코드

WSARecv(sock, wsabuf, 2, ...);

 

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

 

소켓 입출력 모델 - Overlapped 모델( I )

 

* 사용 절차

1. 비동기 입출력을 지원하는 소켓 생성. WSACreateEvent() 함수를 호출하여 대응되는 이벤트 객체도

    같이 생성

 

2. 비동기 입출력을 지원하는 소켓 함수를 호출. WSAOVERLAPPED 구조체의 hEvent 변수에 이벤트

    객체 핸들값을 넣어 전달. 비동기 입출력 작업이 곧바로 완료되지 않으면 소켓 함수는 오류를

    리턴하고 오류코드는 WSA_IO_PENDING 으로 설정된다. 나중에 비동기 입출력 작업이 완료되면

    운영체제는 이벤트 객체를 신호상태로 만들어 이 사실을 애플리케이션에게 알린다.

 

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

4. 비동기 입출력 작업이 완료하여 WSAWaitForMultipleEvents() 함수가 리턴하면,

    WSAGetOverlappedresult() 함수를 호출하여 비동기 입출력 결과를 확인하고 데이터를 처리한다.

 

5. 새로운 소켓을 생성하면 1 ~ 4 를. 그렇지 않으면 2 ~ 4 를 반복

 

 

 

* WSAGetOverlappedResult() 함수

 

BOOl WSAGetOverlappedResult(

    SOCKET s,                                        // 비동기 입출력 함수 호출에 사용한 소켓

    LPWSAOVERLAPPED lpOverlapped,  // 비동기 입출력 함수에 사용한 WSAOVERLAPPED

                                                                 구조체 변수의 주소값

    LPDWORD lpcbTransfer,                 // DWORD형 변수 주소값을 전달하면 이 변수에 전송바이트

                                                               수가 저장

    BOOL fWait,                                 // 비동기 입출력 작업이 끝날때까지 대기하려면 TRUE,

                                                           그렇지 않으면 FALSE 

    LPDWORD lpdwFlags               // DWORD형 변수 주소값을 전달하면 비동기 입출력 작업과

                                                        관련된 부가적인 정보가 저장

);

 

 

 

[Overlapped 모델( I ) 을 이용한 에코 서버]

 

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

 

#define BUFSIZE 512

 

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

 

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

 

// 소켓 입출력 함수
DWORD WINAPI WorkerThread(LPVOID arg);
// 소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock);
void RemoveSocketInfo(int index);
// 오류 출력 함수
void err_quit(char *msg);
void err_display(char *msg);

 

int main(int argc, char* argv[])
{
    int retval;
    InitializeCriticalSection(&cs);
 
    // 윈속 초기화
    WSADATA wsa;
    if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)
        return -1;
 
    // socket()
    SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock == INVALID_SOCKET) err_quit("socket()");
 
    // bind()
    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()");
 
    // listen()
    retval = listen(listen_sock, SOMAXCONN);
    if(retval == SOCKET_ERROR) err_quit("listen()");
 
    // 더미(dummy) 이벤트 객체 생성
    WSAEVENT hEvent = WSACreateEvent();
    if(hEvent == WSA_INVALID_EVENT) err_quit("WSACreateEvent()");
    EventArray[nTotalSockets++] = hEvent;
 
    // 스레드 생성
    DWORD ThreadId;
    HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, &ThreadId);
    if(hThread == NULL) return -1;
    CloseHandle(hThread);
 

    while(1)
    {
        // accept()
        SOCKADDR_IN clientaddr;
        int addrlen = sizeof(clientaddr);
        SOCKET client_sock = accept(listen_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(AddSocketInfo(client_sock) == FALSE) {
            closesocket(client_sock);
            printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
                inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            continue;
        }
  
        // 비동기 입출력 시작
        SOCKETINFO *ptr = SocketInfoArray[nTotalSockets-1];
        DWORD recvbytes;
        DWORD flags = 0;
        int retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1, &recvbytes, &flags,

                                             &(ptr->overlapped), NULL);


        if(retval == SOCKET_ERROR) {
            if(WSAGetLastError() != WSA_IO_PENDING) {
                err_display("WSARecv()");
                RemoveSocketInfo(nTotalSockets-1);
                continue;
            }
        }
  
        // 소켓의 개수(nTotalSockets) 변화를 알림
        if(WSASetEvent(EventArray[0]) == FALSE) {
            err_display("WSASetEvent()");
            break;
        }
    }
    // 윈속 종료
    WSACleanup();
    DeleteCriticalSection(&cs);
    return 0;
}

 

// 비동기 입출력 처리
DWORD WINAPI WorkerThread(LPVOID arg)
{
    int retval;
 
    while(1)

    {
        // 이벤트 객체 관찰
        DWORD index = WSAWaitForMultipleEvents(nTotalSockets, EventArray, FALSE,

                                                                               WSA_INFINITE, FALSE);


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

        index -= WSA_WAIT_EVENT_0;
        WSAResetEvent(EventArray[index]);
        if(index == 0) continue;
  
        // 클라이언트 정보 얻기
        SOCKETINFO *ptr = SocketInfoArray[index];
        SOCKADDR_IN clientaddr;
        int addrlen = sizeof(clientaddr);
        getpeername(ptr->sock, (SOCKADDR *)&clientaddr, &addrlen);
  
        // 비동기 입출력 결과 확인
        DWORD cbTransferred, flags;
        retval = WSAGetOverlappedResult(ptr->sock, &(ptr->overlapped), &cbTransferred,

                                                                 FALSE, &flags);


        if(retval == FALSE || cbTransferred == 0) {
            if(retval == FALSE)
                err_display("WSAGetOverlappedResult()");
            RemoveSocketInfo(index);
            printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",

                                          inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            continue;
        }
       

        // 데이터 전송량 갱신
        if(ptr->recvbytes == 0) {
            ptr->recvbytes = cbTransferred;
            ptr->sendbytes = 0;
            // 받은 데이터 출력
            ptr->buf[ptr->recvbytes] = '\0';
            printf("[TCP/%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr),

                                   ntohs(clientaddr.sin_port), ptr->buf);
        }
        else {    ptr->sendbytes += cbTransferred;    }
  
        if(ptr->recvbytes > ptr->sendbytes) {
            // 데이터 보내기
            ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
            ptr->overlapped.hEvent = EventArray[index];
            ptr->wsabuf.buf = ptr->buf + ptr->sendbytes;
            ptr->wsabuf.len = ptr->recvbytes - ptr->sendbytes;
   
            DWORD sendbytes;
            retval = WSASend(ptr->sock, &(ptr->wsabuf), 1, &sendbytes, 0, &(ptr->overlapped),

                                               NULL);


            if(retval == SOCKET_ERROR) {
                if(WSAGetLastError() != WSA_IO_PENDING) {
                    err_display("WSASend()");
                }
                continue;
            }
        }
        else {
            ptr->recvbytes = 0;
   
            // 데이터 받기
            ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
            ptr->overlapped.hEvent = EventArray[index];
            ptr->wsabuf.buf = ptr->buf;
            ptr->wsabuf.len = BUFSIZE;
   
            DWORD recvbytes;
            flags = 0;
            retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1, &recvbytes, &flags,

                                            &(ptr->overlapped), NULL);
            if(retval == SOCKET_ERROR) {
                if(WSAGetLastError() != WSA_IO_PENDING) {
                    err_display("WSARecv()");
                }
                continue;
            }
        }
    }
}

 

// 소켓 정보 추가
BOOL AddSocketInfo(SOCKET sock)
{
    EnterCriticalSection(&cs);
 
    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;
    }
 
    ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
    ptr->overlapped.hEvent = hEvent;
    ptr->sock = sock;
    ptr->recvbytes = 0;
    ptr->sendbytes = 0;
    ptr->wsabuf.buf = ptr->buf;
    ptr->wsabuf.len = BUFSIZE;
    SocketInfoArray[nTotalSockets] = ptr;
    EventArray[nTotalSockets] = hEvent;
    nTotalSockets++;
 
    LeaveCriticalSection(&cs);
    return TRUE;
}

 

// 소켓 정보 삭제
void RemoveSocketInfo(int index)
{
    EnterCriticalSection(&cs);
 
    SOCKETINFO *ptr = SocketInfoArray[index];
    closesocket(ptr->sock);
    delete ptr;
    WSACloseEvent(EventArray[index]);
 
    for(int i=index; i<nTotalSockets; i++) {
        SocketInfoArray[i] = SocketInfoArray[i+1];
        EventArray[i] = EventArray[i+1];
    }
    nTotalSockets--;
 
    LeaveCriticalSection(&cs);
}

 

// 소켓 함수 오류 출력 후 종료
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);
}

 

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

 

소켓 입출력 모델 - Overlapped 모델( II )

 

[동작 원리]

1. 비동기 입출력 함수를 호출함으로써 운영체제에 입출력 작업을 요청한다.

2. 해당 스레드는 곧바로 alertable wait 상태에 진입한다. alertable wait 상태란 비동기 입출력을 위한

    특별한 대기 상태로, 비동기 입출력 함수를 호출한 스레드가 이 상태에 있어야만 완료 루틴이 

    호출될 수 있다. 스래드를 alertable wait 상태로 만드는 함수는 WaitForSingleObjectEx(),

    WaitForMultipleObjectEx(), ObjectEx(), WSAWaitForMultipleEvents() 등이 있다.

    WSAWaitForMultipleEvents() 는 마지막 인자에 TRUE 를 사용하면 해당 스레드는 alertable wait

    상태가 된다.

 

3. 비동기 입출력 작업이 완료되면 운영체제는 스레드의 APC 큐에 결과를 저장한다.

    APC 큐란 비동기 입출력 결과 저장을 위해 운영체제가 각 스레드마다 할당하는 메모리 영역이다.

 

4. 비동기 입출력 함수를 호출한 스레드가 alertable wait 상태에 있으면 운영체제는 APC 큐에 저장된

    정보(완료 루틴의 주소)를 참조하여 완료 루틴을 호출한다. 완료 루틴 내부에서는 데이터를 처리한

    후 다시 비동기 입출력 함수를 호출할 수 있다.

 

5. APC 큐에 저장된 정보를 토대로 모든 완료루틴 호출이 끝나면 스레드는 alertable wait 상태에서

   빠져나온다. 이 스레드가 비동기 입출력 결과를 계속 처리하려면 다시 alertable wait 상태에

   진입해야 한다.

 

 

 

[입출력 절차] 

1. 비동기 입출력을 지원하는 소켓 생성

2. 비동기 입출력 함수 호출. 이때 완료 루틴의 시작 주소를 함수 인자로 전달.

    비동기 입출력 작업이 곧바로 완료되지 않으면 소켓 함수는 오류를 리턴하고 오류 코드는

    WSA_IO_PENDING 으로 설정.

 

3. 비동기 입출력 함수를 호출한 스레드를 alertable wait 상태로 만든다.

    앞에서 소개한 WaitForSingleObjectEx(), WaitForMultipleObjectEx(), SleepEx(),

    WSAWaitForMultipleEvents() 등 함수에서 적절한 것을 선택하여 사용하면 된다.

 

4. 비동기 입출력 작업이 완료되면 운영체제는 완료 루틴을 호출한다. 완료 루틴에서는 비동기 입출력

    결과를 확인하고 데이터를 처리한다.

 

5. 완료 루틴 호출이 모두 끝나면 스레드는 alertable wait 상태에서 빠져나온다.

6. 새로운 소켓을 생성하면 1 ~ 5 를 그렇지 않으면 2 ~ 5 를 반복한다.

 

 

 

[완료 루틴]

void CALLBACK CompletionRoutine(

    DWORD dwError,

    DWORD cbTransferred,

    LPWSAOVERLAPPED lpOverlapped,

    DWROD dwFlags

);

 

dwError : 비동기 입출력 결과를 나타냄. 오류가 발생하면 이 값은 0 이 아닌 값이 된다.

cbTransferrd : 전송 바이트 수를 나타낸다. 통신 상대가 접속을 종료하면 이 값은 0 이 된다.

lpOverlapped : 비동기 입출력 함수 호출시 넘겨준 WSAOVERLAPPED 구조체의 주소값이 이 인자를

                        통해 다시 넘어온다. Overlapped 모델(II) 에서는 이벤트 객체를 사용하지 않으므로

                        WSAOVERLAPPED 구조체를 완료 루틴 내부에서 직접 사용 할일은 거의 없다

 

dwFlags : 항상 0 이므로 적어도 현재까지는 사용하지 않는다.

 

 

 

[Overlapped 모델( II ) 을 이용한 에코 서버]

 

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

 

#define BUFSIZE 512

 

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

 

SOCKET client_sock;

 

// 소켓 입출력 함수
DWORD WINAPI WorkerThread(LPVOID arg);
void CALLBACK CompletionRoutine(
    DWORD dwError, DWORD cbTransferred,
    LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags
);


// 오류 출력 함수
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()
    SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_sock == INVALID_SOCKET) err_quit("socket()");

 

    // bind()
    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()");
 
    // listen()
    retval = listen(listen_sock, SOMAXCONN);
    if(retval == SOCKET_ERROR) err_quit("listen()");

 

    // 이벤트 객체 생성
    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if(hEvent == NULL) return -1;

 

    // 스레드 생성
    DWORD ThreadId;
    HANDLE hThread = CreateThread(NULL, 0, WorkerThread, (LPVOID)hEvent, 0, &ThreadId);
    if(hThread == NULL) return -1;
    CloseHandle(hThread);

 

    while(1)
    {
        // accept()
        client_sock = accept(listen_sock, NULL, NULL);
        if(client_sock == INVALID_SOCKET) {
            err_display("accept()");
            continue;
        }  
        if(!SetEvent(hEvent)) break;
    }

 

    // 윈속 종료
    WSACleanup();
    return 0;
}

 

// 스레드 함수
DWORD WINAPI WorkerThread(LPVOID arg)
{
    HANDLE hEvent = (HANDLE)arg;
    int retval;

 

    while(1)
    {
        while(1)
        {
            // alertable wait
            DWORD result = WaitForSingleObjectEx(hEvent, INFINITE, TRUE);
            if(result == WAIT_OBJECT_0) break;
            if(result != WAIT_IO_COMPLETION) return -1;
        }

       

        // 접속한 클라이언트 정보 출력
        SOCKADDR_IN clientaddr;
        int addrlen = sizeof(clientaddr);
        getpeername(client_sock, (SOCKADDR *)&clientaddr, &addrlen);
        printf("[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n",
                     inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
  

        // 소켓 정보 구조체 할당과 초기화
        SOCKETINFO *ptr = new SOCKETINFO;
        if(ptr == NULL) {
            printf("[오류] 메모리가 부족합니다!\n");
            return -1;
        }
        ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
        ptr->sock = client_sock;
        ptr->recvbytes = 0;
        ptr->sendbytes = 0;
        ptr->wsabuf.buf = ptr->buf;
        ptr->wsabuf.len = BUFSIZE;

  

        // 비동기 입출력 시작
        DWORD recvbytes;
        DWORD flags = 0;
        retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1, &recvbytes,
            &flags, &(ptr->overlapped), CompletionRoutine);
        if(retval == SOCKET_ERROR) {
            if(WSAGetLastError() != WSA_IO_PENDING) {
                err_display("WSARecv()");
                return -1;
            }
        }
    }

    return 0;
}

 

// 완료 루틴
void CALLBACK CompletionRoutine(
    DWORD dwError, DWORD cbTransferred,
    LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
    int retval;

 

    // 클라이언트 정보 얻기
    SOCKETINFO *ptr = (SOCKETINFO *)lpOverlapped;
    SOCKADDR_IN clientaddr;
    int addrlen = sizeof(clientaddr);
    getpeername(ptr->sock, (SOCKADDR *)&clientaddr, &addrlen);
   

    // 비동기 입출력 결과 확인
    if(dwError != 0 || cbTransferred == 0) {
        if(dwError != 0) err_display(dwError);
        closesocket(ptr->sock);
        printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n",
            inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
        delete ptr;
        return;
    }

 

    // 데이터 전송량 갱신
    if(ptr->recvbytes == 0) {
        ptr->recvbytes = cbTransferred;
        ptr->sendbytes = 0;
    

        // 받은 데이터 출력
        ptr->buf[ptr->recvbytes] = '\0';
        printf("[TCP/%s:%d] %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port),

                          ptr->buf);
    }
    else {    ptr->sendbytes += cbTransferred;    }

 

    if(ptr->recvbytes > ptr->sendbytes) {
        // 데이터 보내기
        ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
        ptr->wsabuf.buf = ptr->buf + ptr->sendbytes;
        ptr->wsabuf.len = ptr->recvbytes - ptr->sendbytes;

        DWORD sendbytes;
        retval = WSASend(ptr->sock, &(ptr->wsabuf), 1, &sendbytes,

            0, &(ptr->overlapped), CompletionRoutine);
       

        if(retval == SOCKET_ERROR) {
            if(WSAGetLastError() != WSA_IO_PENDING) {
                err_display("WSASend()");
                return;
            }
        }
    }
    else {
        ptr->recvbytes = 0;

  

        // 데이터 받기
        ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
        ptr->wsabuf.buf = ptr->buf;
        ptr->wsabuf.len = BUFSIZE;

        DWORD recvbytes;
        DWORD flags = 0;
        retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1, &recvbytes,
            &flags, &(ptr->overlapped), CompletionRoutine);
       

        if(retval == SOCKET_ERROR) {
            if(WSAGetLastError() != WSA_IO_PENDING) {
                err_display("WSARecv()");
                return;
            }
        }
    }
}

 

// 소켓 함수 오류 출력 후 종료
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);
}