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没有释放。