"IOCP 채팅 서버"에 해당되는 글 - 1건

Post

IOCP 채팅 서버 소스 예제


윤성우의 TCP/IP 에코 채팅 서버에서

다른사람들에게 채팅 내용이 전송되도록 수정한 소스이다.


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
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <list>
 
 
#pragma comment(lib, "ws2_32.lib")
 
#define BUF_SIZE 100
#define READ 3
#define WRITE 5
 
int const NAME_SIZE = 10;
 
typedef struct          // socket intfo
 
{
    SOCKET hClntSock;
    SOCKADDR_IN clntAdr;
}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;
 
typedef struct
{
    char id[NAME_SIZE];
    unsigned int level;
     
}USER_INFO, *PUSER_INFO;        // 나중에 PER_HANDLE_DATA 상속하자
 
std::list<lpper_handle_data> UserList; // 다른사람에게 채팅 전달해야하므로 user list 추가
 
DWORD WINAPI EchoThreadMain(LPVOID CompletionPortIO);
 
CRITICAL_SECTION cs;        // UserList 동기화를 위한 Critical Section
 
 
int main()
{
    WSADATA wsaData;
    HANDLE hComPort;
    SYSTEM_INFO sysInfo;
    LPPER_IO_DATA ioInfo;
    LPPER_HANDLE_DATA handleInfo;
 
    SOCKET hServSock;
    SOCKADDR_IN servAdr;
    int recvBytes, i, flags = 0;
 
    if (0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
    {
        printf("WSAStartup() error!");
        exit(1);
    }
 
    hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
    GetSystemInfo(&sysInfo);
 
    for (i = 0; i < (int)sysInfo.dwNumberOfProcessors; ++i)
    {
        // Thread 핸들 정리 추가할 것
        _beginthreadex(NULL, 0, (_beginthreadex_proc_type)EchoThreadMain, (LPVOID)hComPort, 0, NULL);
    }
 
    hServSock = WSASocketW(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &(servAdr.sin_addr));
    servAdr.sin_port = htons(atoi("7777"));
 
    bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
    listen(hServSock, 5);
 
    InitializeCriticalSection(&cs);
 
    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));        // 누군가 접속할 때 마다 소켓 정보를 생성하고
        handleInfo->hClntSock = hClntSock;                                      // hClntSock은 지역변수이므로 저장한다
        memcpy(&(handleInfo->clntAdr), &clntAdr, addrLen);
 
        EnterCriticalSection(&cs);
        UserList.push_back(handleInfo);
        LeaveCriticalSection(&cs);
 
        CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0);  // 접속한 클라 소켓을 IOCP에 등록함
 
        ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));                        // 신규 유저 접속 시 overlapped도 생성한다.
        memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));                       // 그리고 WSARecv 함수로 소켓 io완료 시 반환되는 wasBuf, overlapped 주소를 등록한다
        ioInfo->wsaBuf.len = BUF_SIZE;
        ioInfo->wsaBuf.buf = ioInfo->buffer;
        ioInfo->rwMode = READ;
 
        WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1, (LPDWORD)&recvBytes, (LPDWORD)&flags, &(ioInfo->overlapped), NULL);    // recv가 완료되었을 때 GetQueue...에서 반환됨
    }  
 
    DeleteCriticalSection(&cs);
 
    return 0;
}
 
DWORD WINAPI EchoThreadMain(LPVOID pComPort)
{
    HANDLE hComPort = (HANDLE)pComPort;
    SOCKET sock;
    DWORD bytesTrans;
    LPPER_HANDLE_DATA   handleInfo;
    LPPER_IO_DATA       ioInfo;
    DWORD flags = 0;
    char msg[BUF_SIZE];
 
    while (1)
    {
        GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE);
        sock = handleInfo->hClntSock;
 
        if (READ == ioInfo->rwMode)
        {
            puts("message received!");
 
            if (0 == bytesTrans)
            {
                EnterCriticalSection(&cs);
                std::list<lpper_handle_data>::iterator iter;
                for (iter = UserList.begin(); iter != UserList.end(); ++iter)
                {
                    if (sock == (*iter)->hClntSock)
                    {
                        UserList.erase(iter);
                        break;
                    }
                }
                LeaveCriticalSection(&cs);
 
                closesocket(sock);
                free(handleInfo);
                free(ioInfo);
                continue;
            }
 
            memset(msg, 0, BUF_SIZE);
            memcpy(msg, ioInfo->buffer, BUF_SIZE);
            free(ioInfo);
             
            EnterCriticalSection(&cs);                      // UserList는 여러 스레드가 접근하는 공유자원이므로 접근 시 cs 필수
            std::list<lpper_handle_data>::iterator iter;
            for (iter = UserList.begin(); iter != UserList.end(); ++iter) 
            {
                // 소켓마다 overlapped를 새로 만들어야 한다. 왜냐하면 하나의 overlapped를 여러 소켓이 쓸 수 없기 때문이다.
                ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
                memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
                ioInfo->wsaBuf.buf = msg;                   // msg는 스레드마다 가지고 있는 지역변수이므로 공유자원이 아니므로 cs 사용을 하지 않는다
                ioInfo->wsaBuf.len = bytesTrans;
                ioInfo->rwMode = WRITE;
                WSASend((*iter)->hClntSock, &(ioInfo->wsaBuf), 1, NULL, 0, &(ioInfo->overlapped), NULL);
            }
            LeaveCriticalSection(&cs);
 
            ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA));
            memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED));
            ioInfo->wsaBuf.len = BUF_SIZE;
            ioInfo->wsaBuf.buf = ioInfo->buffer;
            ioInfo->rwMode = READ;
            WSARecv(sock, &(ioInfo->wsaBuf), 1, NULL, &flags, &(ioInfo->overlapped), NULL);
        }
        else
        {
            puts("message sent!");
            free(ioInfo);
        }
    }
     
    return 0;
}
</lpper_handle_data></lpper_handle_data></lpper_handle_data></list></ws2tcpip.h></windows.h></winsock2.h></process.h></stdlib.h></stdio.h>





실행하면 아래처럼 여러사람들에게 메시지가 전송된다.

내가 개인적으로 저장할 용도로 올리는거라서 수정, 추가할 부분이 많다:Q...

( 메시지큐를 이용해서 데이터 받는 부분, 처리부분 분리하기, 패킷 덜 왔을 때 합치기, acceptEx , 메모리풀 등..)



▲ top