2009년 9월 29일 화요일

Overlapped 모델

Overlapped 모델
   지금까지 배운 소켓 입출력 모델(Select, WSAASyncSelect, WSAEventSelect)과는 근본적으로 다른 입출력 방식(비동기 입출력 방식)

 

   동기 입출력 : 어플리케이션은 입출력 함수를 호출한 후 입출력 작업이 끝날 때 까지 대기한다. 입출력 작업이 끝나면, 입출력 함수는 리턴하고 어플리케이션은 입출력 결과를 처리한다.

 

   비동기 입출력 : 어플리케이션은 입출력 함수를 호출한 후 입출력 작업의 완료 여부와 무관하게 다른 작업을 진행할 수 있다. 입출력 작업이 끝나면 운영체제는 작업 완료를 어플리케이션에 알려준다. 이때 어플리케이션은 다른 작업을 중단하고 입출력 결과를 처리하면 된다.

 

 

방법

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

  2. 비동기 입출력을 지원하는 소켓 함수를 호출한다.
       AcceptEx(), ConnectEx(), DisconnectEx(), TransmitFile(), TransmitPackets(),
       WSAIoctl(), WSANSPIoctl(), WSAProviderConfigChange(), WSARecv(), WSARecvFrom(),
       WSARecvMsg(), WSASend(), WSASendTo()

  3. 운영체제는 소켓 입출력 작업 완료를 어플리케이션에 알려주고, 어플리케이션은 입출력 결과를 처리한다.

 

비동기 입출력 함수 중

 

   int WSASend(
       SOCKET s, // 보내고자 하는 소켓
       LPWSABUF lpBuffers, // WSABUF 구조체 배열의 시작 주소다. 각각의 배열 원소는 버퍼의 시작 주소와 길이(바이트단위)를 담고 있다.
       DWORD dwBufferCount, // WSABUF 구조체 배열의 원소 개수
       LPDWORD lpNumberOfBytesSent, // 함수 호출이 성공하면 이 변수에 보내는 바이트 수가 저장된다.
       LPDWORD lpFlags, // send() 함수의 마지막 인자와 동일한 역할을 한다.
       LPWSAOVERLAPPED lpOverlapped, // WSAOVERLAPPED 구조체 변수 포인터, 이 구조체는 비동기 입출력을 위한 정보를 운영체제에 전달하거나, 운영체제가 비동기 입출력 결과를 어플리케이션에 전달할 때 사용한다.
       LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 입출력 작업이 완료되면 운영체제가 자동으로 호출할 완료 루틴(콜백함수)의 주소값이다.
   );

 

   WSARecv() 함수 구조도 WSASend() 함수와 똑같으며, 보낸다와 받는다의 차이만 가지고 있다.

 

   typedef struct __WSABUF {
       u_long len;  // 길이(바이트 단위)
       char FAR* buf; // 버퍼 시작 주소
   } WSABUF, *LPWSABUF;

 

   typedef struct _WSAOVERLAPPED {
       DWORD Internal;      //////////////////////////////
       DWORD InternalHigh;  // 운영체제가 내부적으로 사용하는 값
       DWORD Offset;        //
       DWORD OffsetHigh;    /////////////////////////////
       WSAEVENT hEvent;     // 입출력 작업이 완료되면 hEvent가 가리키는 이벤트 객체는 신호상태가 된다.
   } WSAOVERLAPPED, *LPWSAOVERLAPPED;

 

   특징

     1. 모아서 한번에 전송하거나 받을 수 있다.

       ex)
        buf[128];
        buf[256];
        WSA wsaBuf[2];
        wsabuf[0].buf = buf1;
        wsabuf[0].len = 128;
        wsabuf[1].buf = buf2;
        wsabuf[1].len = 256;
        WSASend(sock, wsabuf, 2, ...); or WSARecv(sock, wsabuf, 2, ...);

     2. 마지막 두 인자에 모두 NULL 값을 사용하면 동기 함수로 동작한다.

     3. hEvent 변수를 사용하거나, lpCompletionRoutine 인자를 사용한다. 단, lpCompletionRoutine 인자의 우선순위가 높으므로 이 값이 NULL이 아니면, WSAOVERLAPPED 구조체의 hEvent 변수는 사용되지 않는다.


WSAOVERLAPPED 구조체의 hEvent 변수를 사용하는 입출력 모델 방법

 

     1. 비동기 입출력을 지원하는 소켓을 생성한다. 이때 WSACreateEvent()함수를 호출하여 대응하는 이벤트 객체도 같이 생성한다.
     2. 비동기 입출력을 지원하는 소켓 함수를 호출한다. 이때 WSAOVERLAPPED 구조체의 hEvent변수에 이벤트 객체 핸들값을 넣어서 전달한다. 비동기 입출력 작업이 곧바로 완료되지 않으면, 소켓 함수는 오류를 리턴하고 오류 코드는 WSA_IO_PENDING으로 설정된다. 나중에 비동기 입출력 작업이 완료되면, 운영체제는 이벤트 객체를 신호 상태로 만들어 이 사실을 어플리케이션에 알린다.
     3. WSAWaitForMultipleEvents() 함수를 호출하여 이벤트 객체가 신호 상태가 되기를 기다린다.
     4. 비동기 입출력 작업이 완료하여 WSAWaitForMultipleEvents() 함수가 리턴하면, WSAGetOverlappedResult() 함수를 호출하여 비동기 입출력 결과를 확인하고 데이터 처리를 한다.
     5. 새로운 소켓을 생성하면 1 ~ 4, 그렇지 않으면 2 ~ 4를 반복한다.


   BOOL WSAGetOverlappedResult(
     SOCKET s, // 비동기 입출력 함수 호출에 사용한 소켓을 다시 넣는다.
     LPWSAOVERLAPPED lpOverlapped, // 비동기 입출력 함수 호출에 사용한 WSAOVERLAPPED 구초제 변수의 주소값을 다시 넣는다.
     LPDWORD lpcbTransfer, // 전송 바이트수가 저장된다.
     BOOL fWait, // 비동기 입출력 작업이 끝날 때까지 대기하려면 TRUE, 그렇지 않으면 FALSE를 사용한다.
     LPDWORD lpdwFlags // 이 값은 거의 사용하지 않으므로 무시해도 된다.

 

<예제>

// OverlappedServer_1.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

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

#define MAX_BUFFER_SIZE 512

// 소켓 정보 저장을 위한 구조체
typedef struct
{
        WSAOVERLAPPED overlapped;
        SOCKET        sock;
        char        recvBuffer[MAX_BUFFER_SIZE + 1];
        int                recvBytes;
        int                sendBytes;
        WSABUF        wsabuf;
} SOCKET_INFO;

 

int g_nTotalSockets = 0;

 

SOCKET_INFO*                g_aSocketInfoArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT                        g_aEventArray[WSA_MAXIMUM_WAIT_EVENTS];
CRITICAL_SECTION        g_CSection;
BOOL                                g_bThreadEnd = FALSE;

// 소켓 입출력 스레드 함수
DWORD WINAPI WorkerThread(LPVOID pParam);

 

// 소켓 관리 함수
BOOL AddSocketInfo(SOCKET clientSocket);
void RemoveSocketInfo(int nIndex);

 

// 오류 출력 함수
void err_quit(char* msg);
void err_display(char* msg);
void err_display(int errCode);

 

int _tmain(int argc, _TCHAR* argv[])
{
        int retValue;
        InitializeCriticalSection(&g_CSection);

 

        // 윈속 초기화
        WSADATA wsa;
        if(WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
                return -1;

        // socket()
        SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
        if(listenSocket == INVALID_SOCKET)
                err_quit("socket()");

 

        // bind()
        SOCKADDR_IN serverAddr;
        memset(&serverAddr, 0, sizeof(serverAddr));
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(5001);
        serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        retValue = bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
        if(retValue == SOCKET_ERROR)
                err_quit("bind()");

 

        //listen()
        retValue = listen(listenSocket, SOMAXCONN);
        if(retValue == SOCKET_ERROR)
                err_quit("listen()");

 

        // 더미(dummy) 이벤트 객체 생성
        WSAEVENT hEvent = WSACreateEvent();
        if(hEvent == WSA_INVALID_EVENT)
                err_quit("WSACreateEvent()");

        g_aEventArray[g_nTotalSockets++] = hEvent;

 

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

 

        // 통신에 사용할 변수
        SOCKET clientSocket;
        SOCKADDR_IN clientAddr;
        int nAddrLength;
        DWORD dwRecvBytes;
        DWORD dwFlags;

        while(1)
        {
                // accept()
                nAddrLength = sizeof(clientAddr);
                clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddr, &nAddrLength);
                if(clientSocket == INVALID_SOCKET)
                {
                        err_display("connect()");
                        continue;
                }

 

                printf("[TCP 서버] 클라이언트 접속: IP 주소 = %s, 포트번호 = %d\n",
                        inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));

 

                // 접속 리스트 추가
                if(AddSocketInfo(clientSocket) == FALSE)
                {
                        closesocket(clientSocket);
                        printf("[TCP 서버] 클라이언트 종료: IP 주소 = %s, 포트번호 = %d\n",
                                inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
                        continue;
                }

 

                // 받기 상태로 설정
                SOCKET_INFO* pSocketInfo = g_aSocketInfoArray[g_nTotalSockets - 1];
                dwFlags = 0;
                int retValue = WSARecv(pSocketInfo->sock, &(pSocketInfo->wsabuf), 1, &dwRecvBytes,
                        &dwFlags, &(pSocketInfo->overlapped), NULL);
                if(retValue == SOCKET_ERROR)
                {
                        if(WSAGetLastError() != WSA_IO_PENDING)
                        {
                                err_display("WSARecv()");
                                RemoveSocketInfo(g_nTotalSockets - 1);
                                continue;
                        }
                }

 

                // 이벤트 갯수 변동에 따른 대기상태 이벤트 배열 재설정 요청
                if(WSASetEvent(g_aEventArray[0]) == FALSE)
                {
                        err_display("WSASetEvent()");
                        break;
                }
        }

        g_bThreadEnd = TRUE;
        WSASetEvent(g_aEventArray[0]);
        WaitForSingleObject(hThread, INFINITE);

 

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

DWORD WINAPI WorkerThread(LPVOID pParam)
{
        int retValue;
        DWORD index;
        SOCKADDR_IN clientAddr;
        int nAddrLength;
        DWORD cbTransferred, dwFlags, dwSendBytes, dwRecvBytes;

        while(1)
        {
                // 이벤트 대기상태
                index = WSAWaitForMultipleEvents(g_nTotalSockets, g_aEventArray, FALSE, WSA_INFINITE, FALSE);
                if(index == WSA_WAIT_FAILED)
                {
                        err_display("WSAWaitForMultipleEvents()");
                        continue;
                }

                index -= WSA_WAIT_EVENT_0;

 

                // 이벤트 재설정
                WSAResetEvent(g_aEventArray[index]);
                if(index == 0) // 만약 0번째 인덱스일 경우 이벤트 배열 재설정 요청을 한 것임
                {
                        if(g_bThreadEnd == TRUE)
                                break;

                        continue;
                }

 

                // 클라이언트 정보 얻기
                SOCKET_INFO* pSocketInfo = g_aSocketInfoArray[index];
                nAddrLength = sizeof(clientAddr);
                getpeername(pSocketInfo->sock, (SOCKADDR*)&clientAddr, &nAddrLength);

 

                // 비동기 입출력 결과 확인
                retValue = WSAGetOverlappedResult(pSocketInfo->sock, &(pSocketInfo->overlapped),
                        &cbTransferred, FALSE, &dwFlags);
                if(retValue == FALSE || cbTransferred == 0)
                {
                        if(retValue == FALSE)
                                err_display("WSAGetOverlappedResult()");

                        RemoveSocketInfo(index);
                        printf("[TCP 서버] 클라이언트 종료: IP 주소 = %s, 포트번호 = %d\n",
                                inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
                        continue;
                }

 

                // 데이터 전송량 갱신
                if(pSocketInfo->recvBytes == 0)
                {
                        pSocketInfo->recvBytes = cbTransferred;
                        pSocketInfo->sendBytes = 0;
                        pSocketInfo->recvBuffer[cbTransferred] = '\0';
                        printf("[TCP/%s:%d] %s\n", inet_ntoa(clientAddr.sin_addr),
                                ntohs(clientAddr.sin_port), pSocketInfo->recvBuffer);
                }
                else
                {
                        pSocketInfo->sendBytes += cbTransferred;
                }

                if(pSocketInfo->recvBytes > pSocketInfo->sendBytes)
                {
                        memset(&(pSocketInfo->overlapped), 0, sizeof(pSocketInfo->overlapped));
                        pSocketInfo->overlapped.hEvent = g_aEventArray[index];
                        pSocketInfo->wsabuf.buf = pSocketInfo->recvBuffer + pSocketInfo->sendBytes;
                        pSocketInfo->wsabuf.len = pSocketInfo->recvBytes - pSocketInfo->sendBytes;

                        retValue = WSASend(pSocketInfo->sock, &(pSocketInfo->wsabuf), 1, &dwSendBytes,
                                0, &(pSocketInfo->overlapped), NULL);

                        if(retValue == SOCKET_ERROR)
                        {
                                if(WSAGetLastError() != WSA_IO_PENDING)
                                        err_display("WSASend()");
                                continue;
                        }
                }
                else
                {
                        pSocketInfo->recvBytes = 0;

 

                        // 데이터 받기
                        memset(&(pSocketInfo->overlapped), 0, sizeof(pSocketInfo->overlapped));
                        pSocketInfo->overlapped.hEvent = g_aEventArray[index];
                        pSocketInfo->wsabuf.buf = pSocketInfo->recvBuffer;
                        pSocketInfo->wsabuf.len = MAX_BUFFER_SIZE;

                        dwFlags = 0;
                        retValue = WSARecv(pSocketInfo->sock, &(pSocketInfo->wsabuf), 1, &dwRecvBytes,
                                &dwFlags, &(pSocketInfo->overlapped), NULL);
                        if(retValue == SOCKET_ERROR)
                        {
                                if(WSAGetLastError() != WSA_IO_PENDING)
                                        err_display("WSARecv()");
                                continue;
                        }
                }
        }

        return 0;
}

BOOL AddSocketInfo(SOCKET clientSocket)
{
        EnterCriticalSection(&g_CSection);

 

        if(g_nTotalSockets >= WSA_MAXIMUM_WAIT_EVENTS)
        {
                printf("[오류] 소켓 정보를 추가할 수 없습니다.\n");
                return FALSE;
        }

        SOCKET_INFO* pSocketInfo = new SOCKET_INFO;
        if(pSocketInfo == NULL)
        {
                printf("[오류] 메모리가 부족합니다.\n");
                return FALSE;
        }

        WSAEVENT hEvent = WSACreateEvent();
        if(hEvent == WSA_INVALID_EVENT)
        {
                err_display("WSACreateEvent()");
                return FALSE;
        }

        memset(&pSocketInfo->overlapped, 0, sizeof(pSocketInfo->overlapped));
        pSocketInfo->overlapped.hEvent = hEvent;
        pSocketInfo->sock = clientSocket;
        pSocketInfo->recvBytes = 0;
        pSocketInfo->sendBytes = 0;
        pSocketInfo->wsabuf.buf = pSocketInfo->recvBuffer;
        pSocketInfo->wsabuf.len = MAX_BUFFER_SIZE;

        g_aSocketInfoArray[g_nTotalSockets] = pSocketInfo;
        g_aEventArray[g_nTotalSockets] = hEvent;
        g_nTotalSockets++;

        LeaveCriticalSection(&g_CSection);
        return TRUE;
}

void RemoveSocketInfo(int nIndex)
{
        EnterCriticalSection(&g_CSection);

        SOCKET_INFO* pInfo = g_aSocketInfoArray[nIndex];

        closesocket(pInfo->sock);
        delete pInfo;
        WSACloseEvent(g_aEventArray[nIndex]);

        for(int i = nIndex; i < g_nTotalSockets; ++i)
        {
                g_aSocketInfoArray[i] = g_aSocketInfoArray[i + 1];
                g_aEventArray[i] = g_aEventArray[i + 1];
        }

        g_nTotalSockets--;

        LeaveCriticalSection(&g_CSection);
}

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\n", 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);
}

댓글 1개:

  1. Wynn casino & hotel - Mapyro
    › casinos › wynn- › casinos › wynn- This map shows the 문경 출장마사지 Wynn, Encore 문경 출장안마 Tower Suites and Wynn Palace rates. Location. This map shows the Wynn, Encore Tower Suites and Wynn Palace rates. Rating: 3.5 1,083 여주 출장샵 reviews 전주 출장안마 Price range: $ (Based on 용인 출장마사지 Average Nightly Rates for a Standard Room from our Partners)

    답글삭제