ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [IOCP] IOCP 서버 및 클라이언트 에코 예제
    C_C++ 프로그래밍/IOCP 2019. 7. 6. 14:43

     

         로재의 개발 일기      

    개발 환경

     OS

     Windows 10

     컴파일러

    VC++ 2017


    IOCP Echo

    예제 및 git에서 얻은 정보를 토대로 짜집기 하여
    직접 구현한 서버입니다.

    현재 서버가 클라이언트에게 데이터를 받으면, 다른 클라이언트들에게도

    데이터를 전송을 하는 부분은 구현은 되었지만... (vector 사용)

    클라이언트가 봉쇄 통신을 하고 있기 때문에, 이 부분은 아직 미구현입니다(대충 복붙하였습니다)

    (구현 하면 채팅이 가능하게 될 것으로 예상은 합니다.)


    ( 학습 및 테스트 용으로 구현하였으니, 테스트 혹은 학습 용도로 사용하시는 것을 추천드립니다.)

    퀄리티가 떨어져서..



    수정해야 하는 부분

    난잡한 논리 흐름 (비동기화를 적절히 사용한 흐름), 클라이언트 비동기화 구현

    추후에는 상속 및 캡슐화를 해보려고 합니다.

    클라이언트에게 데이터를 뿌리는 기능도 추가 예정






      IOCP_Server.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    #include <iostream>
    #include <stdlib.h>
    #include <process.h>
    #include <winsock2.h>
    #include <windows.h>
    #include <vector>
    #include <Ws2tcpip.h> //inet_pton 
     
    // vs warning and winsock error 
    #pragma comment(lib, "ws2_32.lib")
    #pragma warning (disable : 4996)
     
    constexpr int PORT_NUM = 3800;
    constexpr int BUF_SIZE = 1024;
    constexpr int READ = 3;
    constexpr int WRITE = 5;
     
    typedef struct    // socket info
    {
        SOCKET hClntSock;
        SOCKADDR_IN clntAdr;
        CHAR ip[22];
    } PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
     
    typedef struct    // buffer info
    {
        OVERLAPPED overlapped;
        WSABUF wsaBuf;
        char buffer[BUF_SIZE];
        INT rwMode;    // READ or WRITE
    } PER_IO_DATA, *LPPER_IO_DATA;
     
    // 클라이언트 통신을 위한 vector container
    std::vector<LPPER_HANDLE_DATA> UserList;
     
    void ErrorHandling(const char *message);
    unsigned __stdcall ThreadMain(void * CompletionPortIO);
     
    int main(int argc, char* argv[])
    {
        WSADATA    wsaData;
        HANDLE hComPort;
        SYSTEM_INFO sysInfo;
        LPPER_IO_DATA ioInfo;
        LPPER_HANDLE_DATA handleInfo;
     
        SOCKET hServSock;
        SOCKADDR_IN servAdr;
        DWORD recvBytes, flags = 0;
        INT i;
     
        // winsock start
        if (WSAStartup(MAKEWORD(22), &wsaData) != 0)
            ErrorHandling("WSAStartup Error");
     
        hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL00);
        GetSystemInfo(&sysInfo);
     
        // main thread와 연결된 thread 생성
        for (i = 0; i < sysInfo.dwNumberOfProcessors; i++)
            _beginthreadex(NULL0, ThreadMain, (LPVOID)hComPort, 0NULL);
     
        // socket 설정
        hServSock = WSASocket(AF_INET, SOCK_STREAM, 0NULL0, WSA_FLAG_OVERLAPPED);
        memset(&servAdr, 0sizeof(servAdr));
        servAdr.sin_family = AF_INET;
        servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
        servAdr.sin_port = htons(PORT_NUM);
     
        // bind and listen q
        bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
        listen(hServSock, 5);
     
        while (1)
        {
            SOCKET hClntSock;
            SOCKADDR_IN clntAdr;
            int addrLen = sizeof(clntAdr);
     
            hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);
     
            handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA));        // LPPER_HANDLE_DATA 초기화
            inet_ntop(AF_INET, &clntAdr.sin_addr, handleInfo->ip, INET_ADDRSTRLEN);    // get new client ip
            handleInfo->hClntSock = hClntSock;                                        // 클라이언트의 정보를 구조체에 담아 놓는다.
            printf("New Client Access : %s\n", handleInfo->ip);
            memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
     
            // 소켓 입출력 포트에 accept을 통해서 return 된 클라이언트 정보를 묶는다.
            CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);
     
            // 클라이언트 user data 초기화
            UserList.push_back(handleInfo);
     
            // 클라이언트가 가지게 될 data 초기화
            ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
            memset(&(ioInfo->overlapped), 0sizeof(OVERLAPPED));
            memset(ioInfo->buffer, 0x00, BUF_SIZE);
            ioInfo->wsaBuf.len = BUF_SIZE;
            ioInfo->wsaBuf.buf = ioInfo->buffer;
            ioInfo->rwMode = READ;
     
            // 비동기 입출력 시작
            WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1&recvBytes, &flags, &(ioInfo->overlapped), NULL);
        }
        return 0;
    }
     
    unsigned __stdcall ThreadMain(void * pComPort)
    {
        HANDLE hComPort = (HANDLE)pComPort;
        SOCKET sock;
        DWORD bytesTrans;
        LPPER_HANDLE_DATA handleInfo;
        LPPER_IO_DATA ioInfo;
        int flags = 0;
        CHAR message[BUF_SIZE];
     
        while (1)
        {
            // 비동기 입출력 완료를 기다린다.
            GetQueuedCompletionStatus(hComPort, &bytesTrans,(LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
            // 클라이언트의 socket을 가져온다.
            sock = handleInfo->hClntSock;
     
            // 첫 시작은 읽기 상태
            if (ioInfo->rwMode == READ)
            {
                puts("message received!");
     
                // critical section
                // EOF 전송, client exit
                if (bytesTrans == 0)
                {
                    std::vector<LPPER_HANDLE_DATA>::iterator iter;
                    for (iter = UserList.begin(); iter != UserList.end(); ++iter) {
                        if ((*iter)->hClntSock == sock) {
                            UserList.erase(iter);
                            break;
                        }
                    }
                    closesocket(sock);
                    free(handleInfo);
                    free(ioInfo);
                    continue;
                }
                // critical section end
     
                // 클라이언트가 가진 데이터 구조체의 정보를 바꾼다.
                // 이젠 서버가 쓰기를 행함
        
                /*  (추가 내용)
                    handling을 사용하여
                    다중 클라이언트에게 전달 */
                std::vector<LPPER_HANDLE_DATA>::iterator iter;
                memcpy(message, ioInfo->wsaBuf.buf, BUF_SIZE);
                message[bytesTrans] = '\0';            // 문자열의 끝에 \0을 추가한다 (쓰레기 버퍼 방지)
                printf("Sock[%d] : %s\n", sock, message);    //test
                free(ioInfo);
     
                for (iter = UserList.begin(); iter != UserList.end(); ++iter) {
                    ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
                    memset(&(ioInfo->overlapped), 0x00sizeof(OVERLAPPED));
                    int len = strlen(message);
                    ioInfo->wsaBuf.len = len;
                    strcpy(ioInfo->buffer, message);
                    ioInfo->wsaBuf.buf = ioInfo->buffer;
                    ioInfo->rwMode = WRITE;
     
                    if (WSASend((*iter)->hClntSock, &(ioInfo->wsaBuf), 1&bytesTrans, 0&(ioInfo->overlapped), NULL== SOCKET_ERROR)
                    {
                        if (WSAGetLastError() != WSA_IO_PENDING)
                            ErrorHandling("WSASend() error");
                    }
                }
                // 데이터 구조체 초기화, 쓰기 -> 읽기 모드로 변경
                ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
                memset(&(ioInfo->overlapped), 0x00sizeof(OVERLAPPED));
                ioInfo->wsaBuf.len = BUF_SIZE;
                ioInfo->wsaBuf.buf = ioInfo->buffer;
                ioInfo->rwMode = READ;
     
                // 읽기 시작
                if (WSARecv(sock, &(ioInfo->wsaBuf), 1&bytesTrans, (LPDWORD)&flags, &(ioInfo->overlapped), NULL== SOCKET_ERROR)
                {
                    if (WSAGetLastError() != WSA_IO_PENDING)
                        ErrorHandling("WSASend() error");
                }
            }
            // 쓰기 상태
            else {
                printf("Message Sent!\n");
                free(ioInfo);
            }
        }
        return 0;
    }
     
    void ErrorHandling(const char *message)
    {
        perror(message);
        exit(1);
    }
    cs


      IOCP_Client.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    #include <WinSock2.h>
    #include <iostream>
    #include <stdio.h>
    #include <tchar.h>
     
    using namespace std;
     
    #define    MAX_BUFFER        1024
    #define SERVER_PORT        3800
    #define SERVER_IP        "127.0.0.1"
     
    #pragma warning (disable : 4996)
    #pragma comment(lib, "ws2_32.lib")
     
    struct stSOCKETINFO
    {
        WSAOVERLAPPED    overlapped;
        WSABUF            dataBuf;
        SOCKET            socket;
        char            messageBuffer[MAX_BUFFER];
        int                recvBytes;
        int                sendBytes;
    };
     
    int main()
    {
        WSADATA wsaData;
        // 윈속 버전을 2.2로 초기화
        int nRet = WSAStartup(MAKEWORD(22), &wsaData);
        if (nRet != 0) {
            std::cout << "Error : " << WSAGetLastError() << std::endl;
            return false;
        }
     
        // TCP 소켓 생성
        SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (clientSocket == INVALID_SOCKET) {
            std::cout << "Error : " << WSAGetLastError() << std::endl;
            return false;
        }
     
        std::cout << "socket initialize success." << std::endl;
     
        // 접속할 서버 정보를 저장할 구조체
        SOCKADDR_IN stServerAddr;
     
        char    szOutMsg[MAX_BUFFER];
        char    sz_socketbuf_[MAX_BUFFER];
        stServerAddr.sin_family = AF_INET;
        // 접속할 서버 포트 및 IP
        stServerAddr.sin_port = htons(SERVER_PORT);
        stServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP);
     
        nRet = connect(clientSocket, (sockaddr*)&stServerAddr, sizeof(sockaddr));
        if (nRet == SOCKET_ERROR) {
            std::cout << "Error : " << WSAGetLastError() << std::endl;
            return false;
        }
     
        std::cout << "Connection success..." << std::endl;
        while (true) {
            memset(szOutMsg, 0x00, MAX_BUFFER);
            std::cout << ">>";
            std::cin >> szOutMsg;
            if (_strcmpi(szOutMsg, "quit"== 0break;
     
            int nSendLen = send(clientSocket, szOutMsg, strlen(szOutMsg), 0);
     
            if (nSendLen == -1) {
                std::cout << "Error : " << WSAGetLastError() << std::endl;
                return false;
            }
     
            std::cout << "Message sended : bytes[" << nSendLen << "], message : [" <<
                szOutMsg << "]" << std::endl;
     
            memset(sz_socketbuf_, 0x00, MAX_BUFFER);
            int nRecvLen = recv(clientSocket, sz_socketbuf_, 10240);
            if (nRecvLen == 0) {
                std::cout << "Client connection has been closed" << std::endl;
                closesocket(clientSocket);
                return false;
            }
            else if (nRecvLen == -1) {
                std::cout << "Error : " << WSAGetLastError() << std::endl;
                closesocket(clientSocket);
                return false;
            }
     
            std::cout << "Message received : bytes[" << nRecvLen << "], message : [" <<
                sz_socketbuf_ << "]" << std::endl;
        }
     
        closesocket(clientSocket);
        std::cout << "Client has been terminated..." << std::endl;
     
        return 0;
    }
    cs



      실행결과


    클라이언트에게 보내는 기능을 현재 Server가 하고 있지만
    Client는 현재 비봉쇄 통신을 하고 있지 않기 때문에 받지 못하고 있습니다.



    마무리

    winsock MSDN 삽질하다 보니까 슬슬 알게 될 것 같은 느낌적인 느낌이다.
    그리고 iocp는 학습자료 예제가 부족한 것 같습니다. (죄다 에코..)

    ※ 본 글은 개인 포트폴리오 혹은 공부용으로 사용하기 때문에, 무단 복사 유포는 금지하지만, 개인 공부 용도로는 얼마든지 사용하셔도 좋습니다



    반응형
Designed by Tistory.