Windows下的高效网络模型IOCP完整示例
IOCP即完成端口(I/O Completion Port),与Linux下的epoll一样,是一种非常高效的网络模型。epoll 是当资源准备就绪时发出可处理通知消息;IOCP 则是当事件完成时发出完成通知消息。
epoll模型就好比去银行办事: 1.到银行排队取号 2.等待期间,可以忙点其它的事情 3.号到了,通知你去窗口办理(需要你自己去办理,有一个办理过程) 4.办理完后离开
IOCP模型就好比去打印资料: 1.到打印店,把资料给老板进行排队处理 2.等待期间,可以忙点其它的事情 3.打印好了,通知你去拿(不需要你自己去打印,老板已经帮你打印好了) 4.办理完后离开
通过类比描述,可以看出,IOCP更先进一些,下面来看看IOCP的实例:
client.c:
1#include <WinSock2.h>
2#include <stdio.h>
3#include <stdlib.h>
4
5#pragma comment(lib, "ws2_32.lib")
6
7int main()
8{
9 WSADATA wsaData;
10 WSAStartup(MAKEWORD(2, 2), &wsaData);
11
12 const char *IP = "127.0.0.1";
13 int port = 6000;
14 struct sockaddr_in server;
15 server.sin_family = AF_INET;
16 server.sin_port = htons(port);
17 server.sin_addr.S_un.S_addr = inet_addr(IP);
18
19 SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
20 int ret = connect(client, (struct sockaddr *)&server, sizeof(server));
21 if (ret < 0)
22 {
23 printf("connect %s:%d failed\n", IP, port);
24 return 1;
25 }
26
27 char buffer[1024];
28 int idx = 0;
29 while (1)
30 {
31 int n = sprintf(buffer, "No:%d", ++idx);
32 send(client, buffer, n, 0);
33 memset(buffer, 0, sizeof(buffer));
34 int rev = recv(client, buffer, sizeof(buffer), 0);
35 if (rev == 0)
36 {
37 printf("Recv Nothing\n");
38 }
39 else
40 {
41 printf("Recv: %s\n", buffer);
42 }
43 Sleep(5000);
44 }
45 closesocket(client);
46 WSACleanup();
47 return 0;
48}
server.c:
1#include <winsock2.h>
2#include <windows.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <signal.h>
6#include <fcntl.h>
7
8#pragma comment(lib, "ws2_32.lib")
9
10typedef enum IoKind
11{
12 IoREAD,
13 IoWRITE
14} IoKind;
15
16typedef struct IoData
17{
18 OVERLAPPED Overlapped;
19 WSABUF wsabuf;
20 DWORD nBytes;
21 IoKind opCode;
22 SOCKET cliSock;
23} IoData;
24
25BOOL PostRead(IoData *data)
26{
27 memset(&data->Overlapped, 0, sizeof(data->Overlapped));
28 data->nBytes = data->wsabuf.len;
29 data->opCode = IoREAD;
30
31 DWORD dwFlags = 0;
32 int nRet = WSARecv(data->cliSock, &data->wsabuf, 1, &data->nBytes, &dwFlags, &data->Overlapped, NULL);
33 if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
34 {
35 printf("WASRecv Failed:%s\n", WSAGetLastError());
36 closesocket(data->cliSock);
37 free(data->wsabuf.buf);
38 free(data);
39 return FALSE;
40 }
41 return TRUE;
42}
43
44BOOL PostWrite(IoData *data, DWORD nSendLen)
45{
46 memset(&data->Overlapped, 0, sizeof(data->Overlapped));
47 data->nBytes = nSendLen;
48 data->opCode = IoWRITE;
49 int nRet = WSASend(data->cliSock, &data->wsabuf, 1, &data->nBytes, 0, &(data->Overlapped), NULL);
50 if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
51 {
52 printf("WASSend Failed:%s", WSAGetLastError());
53 closesocket(data->cliSock);
54 free(data->wsabuf.buf);
55 free(data);
56 return FALSE;
57 }
58 return TRUE;
59}
60
61DWORD WINAPI WorkerThread(HANDLE hIOCP)
62{
63 IoData *ctx = NULL;
64 DWORD dwIoSize = 0;
65 void *lpCompletionKey = NULL;
66 LPOVERLAPPED lpOverlapped = NULL;
67
68 while (1)
69 {
70 GetQueuedCompletionStatus(hIOCP, &dwIoSize, (PULONG_PTR)&lpCompletionKey, (LPOVERLAPPED *)&lpOverlapped, INFINITE);
71 ctx = (IoData *)lpOverlapped;
72 if (dwIoSize == 0)
73 {
74 if (ctx == NULL)
75 {
76 printf("WorkerThread Exit...\n");
77 break;
78 }
79 printf("Client:%d disconnect\n", ctx->cliSock);
80 closesocket(ctx->cliSock);
81 free(ctx->wsabuf.buf);
82 free(ctx);
83 continue;
84 }
85 if (ctx->opCode == IoREAD)
86 {
87 ctx->wsabuf.buf[dwIoSize] = 0;
88 printf("%s\n", ctx->wsabuf.buf);
89 PostWrite(ctx, dwIoSize);
90 }
91 else if (ctx->opCode == IoWRITE)
92 {
93 PostRead(ctx);
94 }
95 }
96 return 0;
97}
98
99static BOOL IsExit = FALSE;
100
101void OnSignal(int sig)
102{
103 IsExit = TRUE;
104 printf("Recv exit signal...\n");
105}
106
107void SetNonblocking(int fd)
108{
109 unsigned long ul = 1;
110 int ret = ioctlsocket(fd, FIONBIO, &ul);
111 if (ret == SOCKET_ERROR)
112 {
113 printf("set socket:%d non blocking failed:%s\n", WSAGetLastError());
114 }
115}
116
117void NetWork(int port)
118{
119 WSADATA wsaData;
120 WSAStartup(MAKEWORD(2, 2), &wsaData);
121
122 SOCKET m_socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
123
124 struct sockaddr_in server;
125 server.sin_family = AF_INET;
126 server.sin_port = htons(port);
127 server.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
128
129 bind(m_socket, (struct sockaddr *)&server, sizeof(server));
130 listen(m_socket, 0);
131
132 SYSTEM_INFO sysInfo;
133 GetSystemInfo(&sysInfo);
134 int threadCount = sysInfo.dwNumberOfProcessors;
135
136 HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadCount);
137 for (int i = 0; i < threadCount; ++i)
138 {
139 HANDLE hThread;
140 DWORD dwThreadId;
141 hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, &dwThreadId);
142 CloseHandle(hThread);
143 }
144 SetNonblocking(m_socket);
145 while (!IsExit)
146 {
147 SOCKET cliSock = accept(m_socket, NULL, NULL);
148 if (cliSock == SOCKET_ERROR)
149 {
150 Sleep(10);
151 continue;
152 }
153 printf("Client:%d connected.\n", cliSock);
154 if (CreateIoCompletionPort((HANDLE)cliSock, hIOCP, 0, 0) == NULL)
155 {
156 printf("Binding Client Socket to IO Completion Port Failed:%s\n", GetLastError());
157 closesocket(cliSock);
158 }
159 else
160 {
161 IoData *data = (IoData *)malloc(sizeof(IoData));
162 memset(data, 0, sizeof(IoData));
163 data->wsabuf.buf = (char*)malloc(1024);
164 data->wsabuf.len = 1024;
165 data->cliSock = cliSock;
166 PostRead(data);
167 }
168 }
169 PostQueuedCompletionStatus(hIOCP, 0, 0, 0);
170 closesocket(m_socket);
171 WSACleanup();
172}
173
174int main()
175{
176 SetConsoleOutputCP(65001);
177 signal(SIGINT, OnSignal);
178 NetWork(6000);
179 printf("exit\n");
180 return 0;
181}
CMakeLists.txt:
1cmake_minimum_required(VERSION 3.0.0)
2project(t VERSION 0.1.0)
3
4include(CTest)
5enable_testing()
6
7add_executable(server server.c)
8add_executable(client client.c)
9
10target_link_libraries(server ws2_32)
11target_link_libraries(client ws2_32)
12
13set(CPACK_PROJECT_NAME ${PROJECT_NAME})
14set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
15include(CPack)
以上源码在MinGW下编译运行成功。
如果想要添加MiniDump可以添加前文的 MiniDump不生成或者生成0字节的MiniDump.c
代码中调用InitMiniDump(),并修改CMakeLists.txt:
1cmake_minimum_required(VERSION 3.0.0)
2project(t VERSION 0.1.0)
3
4include(CTest)
5enable_testing()
6
7add_executable(server server.c MiniDump.c)
8add_executable(client client.c)
9
10target_link_libraries(server ws2_32 dbghelp)
11target_link_libraries(client ws2_32)
12
13set(CPACK_PROJECT_NAME ${PROJECT_NAME})
14set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
15include(CPack)
16
17# 如果不是使用的MS VC编译器,则需要自行生成PDB文件,方便VS调试Dump文件
18# 这里使用cv2pdb(https://github.com/rainers/cv2pdb)工具来生成,需要自行下载,
19if(NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
20add_custom_command(TARGET server POST_BUILD
21 COMMAND ../cv2pdb64 server.exe
22 DEPENDS server.exe
23)
24endif()
在CMakeLists.txt中有一个判断,如果不是使用的MS VC编译器,则需要自行生成PDB文件,方便VS调试Dump文件,使用 cv2pdb工具来生成,需要自行下载。
需要注意的是,服务器端,如果有连接时,关闭服务器会有内存泄漏,即IoData及其消息Buffer没有释放。
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2023/2023-03-23-Windows下的高效网络模型IOCP完整示例/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。