ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [IOCP] 채팅 서버 및 테스트 클라이언트 예제
    C_C++ 프로그래밍/IOCP 2019. 7. 10. 16:04

          로재의 개발 일기      

    IOCP 채팅 예제

    IOCP에 대해서 의도치 않은 삽질을 하다보니

    어느 정도 소스가 읽히고 (영어 실력도 늘어버린 것 같다..)

    그러면서 클라이언트도 IOCP 방식으로 구현을 하려고 했으나

    실패하였다.


    결국에 스레드 2개를 호출하는 방식으로 바꾸게 되었고

    구글링을 하여 적당한 소스를 긁어 수정하여 구현하였다.


    소스를 확인하면 알 수 있겠지만

    - mutex sock;

    - mutex push_lock;

    - mutex erase_lock;


    총 3개의 뮤텍스를 설정하여

    클라이언트가 무작위로 방문하더라도 생기는 데이터 공유의 문제를 해결하였다.


    아직 부족한 부분이 많기 때문에 좀 더 수정해야 하는 소스이지만

    정상적으로 작동은 하기 때문에 올립니다.



      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
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    #include <iostream>
    #include <stdlib.h>
    #include <process.h>
    #include <winsock2.h>
    #include <windows.h>
    #include <vector>
    #include <Ws2tcpip.h> //inet_pton 
    #include <mutex>
    // vs warning and winsock error 
    #pragma comment(lib, "ws2_32.lib")
    #pragma warning (disable : 4996)
     
    std::mutex push_lock;
    std::mutex erase_lock;
    std::mutex sock_lock;
     
    constexpr int PORT_NUM = 3800;
    constexpr int BUF_SIZE = 1024;
    constexpr int READ = 3;
    constexpr int WRITE = 5;
    constexpr int CLIENT_SIZE = 3000;
    int login_count = 0;
    int logout_count = 0;
     
    typedef struct    // socket info
    {
        SOCKET hClntSock;
        SOCKADDR_IN clntAdr;
        CHAR name[20];
        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, IPPROTO_TCP, NULL0, 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, CLIENT_SIZE);
     
        while (1)
        {
            sock_lock.lock();
     
            SOCKET hClntSock;
            SOCKADDR_IN clntAdr;
            int addrLen = sizeof(clntAdr);
     
            hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &addrLen);
     
            sock_lock.unlock();
            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;                                // 클라이언트의 정보를 구조체에 담아 놓는다.
            std::cout << ++login_count << '\n';
            printf("New Client Access : %s\n", handleInfo->ip);
            memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
     
            // 소켓 입출력 포트에 accept을 통해서 return 된 클라이언트 정보를 묶는다.
            CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);
     
            push_lock.lock();
            // 클라이언트 user data 초기화
            UserList.push_back(handleInfo);
            
            push_lock.unlock();
     
            // 클라이언트가 가지게 될 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;
     
            // name 받기
            recv(handleInfo->hClntSock, handleInfo->name, 200);
            // 비동기 입출력 시작
            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("\nmessage received!");
                if (bytesTrans == 0)
                {
                    erase_lock.lock();
                    std::vector<LPPER_HANDLE_DATA>::iterator iter;
                    for (iter = UserList.begin(); iter != UserList.end(); ++iter) {
                        if ((*iter)->hClntSock == sock) {
                            std::cout << "Logout >> " 
                                << "IP :"<<(*iter)->ip << ", " 
                                << "Sock :" <<(*iter)->hClntSock << ", "
                                << "name :" <<(*iter)->name << '\n';
                            UserList.erase(iter);
                            std::cout << ++logout_count << '\n';
                            break;
                        }
                    }
                    closesocket(sock);
                    free(handleInfo);
                    free(ioInfo);
                    erase_lock.unlock();
                    continue;
                }
                memcpy(message, ioInfo->wsaBuf.buf, BUF_SIZE);
                message[bytesTrans] = '\0';            // 문자열의 끝에 \0을 추가한다 (쓰레기 버퍼 방지)
     
                /* name 나누기
                char *ptr = strtok(message, "]");    // [] => ']'기준으로 나눈다.
                strcpy_s(handleInfo->name, ptr + 1);    // ]으로 나눈 name
                ptr = strtok(NULL, "]");            // ]으로 다시 나눈 message
                strcpy_s(message, ptr);                // message와 name이 나누어진다.
                */
                printf("name : %s\n", handleInfo->name);
                printf("Sock[%d] : %s\n", sock, message);
     
                // 클라이언트가 가진 데이터 구조체의 정보를 바꾼다.
                // 이젠 서버가 쓰기를 행함
                std::vector<LPPER_HANDLE_DATA>::iterator iter;
     
                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_s(ioInfo->buffer, message);
                    ioInfo->wsaBuf.buf = ioInfo->buffer;
                    ioInfo->rwMode = WRITE;
                    if (bytesTrans == 0)
                    {
                        closesocket(sock);
                        free(handleInfo);
                        free(ioInfo);
                        continue;
                    }
     
                    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 <iostream>
    #include <stdlib.h>
    #include <string.h>
    #include <Windows.h>
    #include <process.h>
     
    // vs warning and winsock error 
    #pragma comment(lib, "ws2_32.lib")
    #pragma warning (disable : 4996)
     
    #define BUF_SIZE 1024
    #define NAME_SIZE 20
     
    unsigned WINAPI SendMsg(void* arg);//쓰레드 전송함수
    unsigned WINAPI RecvMsg(void* arg);//쓰레드 수신함수
    void ErrorHandling(const char* msg);
     
    char name[NAME_SIZE] = "[DEFAULT]";
    char msg[BUF_SIZE];
     
    int main(int argc, char* argv[]) {
        WSADATA wsaData;
        SOCKET sock;
        SOCKADDR_IN serverAddr;
        HANDLE sendThread, recvThread;
        if (argc != 4) {
            printf("Usage : %s <IP> <port> <name>\n", argv[0]);
            exit(1);
        }
        std::cout << "connect........\n";
        if (WSAStartup(MAKEWORD(22), &wsaData) != 0)// 윈도우 소켓을 사용한다고 운영체제에 알림
            ErrorHandling("WSAStartup() error!");
     
        sprintf(name, "%s", argv[3]);
        sock = socket(PF_INET, SOCK_STREAM, 0);//소켓을 하나 생성한다.
     
        memset(&serverAddr, 0sizeof(serverAddr));
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
        serverAddr.sin_port = htons(atoi(argv[2]));
     
        if (connect(sock, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)//서버에 접속한다.
            ErrorHandling("connect() error");
     
        // 접속에 성공하면 이 줄 아래가 실행된다.
        std::cout << "Connect Success!\n";
        std::cout << "Sending client's name\n";
        send(sock, name, sizeof(name), 0);
        std::cout << "Success!\n";
     
        sendThread = (HANDLE)_beginthreadex(NULL0, SendMsg, (void*)&sock, 0NULL);//메시지 전송용 쓰레드가 실행된다.
        recvThread = (HANDLE)_beginthreadex(NULL0, RecvMsg, (void*)&sock, 0NULL);//메시지 수신용 쓰레드가 실행된다.
     
        WaitForSingleObject(sendThread, INFINITE);//전송용 쓰레드가 중지될때까지 기다린다./
        WaitForSingleObject(recvThread, INFINITE);//수신용 쓰레드가 중지될때까지 기다린다.
        //클라이언트가 종료를 시도한다면 이줄 아래가 실행된다.
        closesocket(sock);//소켓을 종료한다.
        WSACleanup();//윈도우 소켓 사용중지를 운영체제에 알린다.
        std::cout << "Client exit\n";
        return 0;
    }
     
    unsigned WINAPI SendMsg(void* arg) {//전송용 쓰레드함수
        SOCKET sock = *((SOCKET*)arg);//서버용 소켓을 전달한다.
        char msg[BUF_SIZE];
        while (1) {//반복
            fgets(msg, BUF_SIZE, stdin);//입력을 받는다.
            if (!strcmp(msg, "q\n"|| !strcmp(msg, "Q\n")) {//q를 입력하면 종료한다.
                closesocket(sock);
                send(sock, ""00);
                exit(0);
            }
            send(sock, msg, strlen(msg), 0);//nameMsg를 서버에게 전송한다.
        }
        return 0;
    }
     
    unsigned WINAPI RecvMsg(void* arg) {
        SOCKET sock = *((SOCKET*)arg);//서버용 소켓을 전달한다.
        char msg[NAME_SIZE + BUF_SIZE];
        int strLen;
        while (1) {//반복
            strLen = recv(sock, msg, NAME_SIZE + BUF_SIZE - 10);//서버로부터 메시지를 수신한다.
            if (strLen == -1)
                return -1;
            msg[strLen] = 0;//문자열의 끝을 알리기 위해 설정
            std::cout << ">>" << msg << '\n';
        }
        return 0;
    }
     
    void ErrorHandling(const char* msg) {
        fputs(msg, stderr);
        fputc('\n', stderr);
        exit(1);
    }
     
     
    cs



      테스트 결과





    마무리

    삽질도 나름 공부인 것 같다.


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


    반응형

    'C_C++ 프로그래밍 > IOCP' 카테고리의 다른 글

    [IOCP] IOCP 서버 및 클라이언트 에코 예제  (0) 2019.07.06
Designed by Tistory.