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:

#include 
#include 
#include 

#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	const char *IP = "127.0.0.1";
	int port = 6000;
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.S_un.S_addr = inet_addr(IP);

	SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	int ret = connect(client, (struct sockaddr *)&server, sizeof(server));
	if (ret < 0)
	{
		printf("connect %s:%d failed\n", IP, port);
		return 1;
	}

	char buffer[1024];
	int idx = 0;
	while (1)
	{
		int n = sprintf(buffer, "No:%d", ++idx);
		send(client, buffer, n, 0);
		memset(buffer, 0, sizeof(buffer));
		int rev = recv(client, buffer, sizeof(buffer), 0);
		if (rev == 0)
		{
			printf("Recv Nothing\n");
		}
		else
		{
			printf("Recv: %s\n", buffer);
		}
		Sleep(5000);
	}
	closesocket(client);
	WSACleanup();
	return 0;
}

server.c:

#include 
#include 
#include 
#include 
#include 
#include 

#pragma comment(lib, "ws2_32.lib")

typedef enum IoKind
{
	IoREAD,
	IoWRITE
} IoKind;

typedef struct IoData
{
	OVERLAPPED Overlapped;
	WSABUF wsabuf;
	DWORD nBytes;
	IoKind opCode;
	SOCKET cliSock;
} IoData;

BOOL PostRead(IoData *data)
{
	memset(&data->Overlapped, 0, sizeof(data->Overlapped));
	data->nBytes = data->wsabuf.len;
	data->opCode = IoREAD;

	DWORD dwFlags = 0;
	int nRet = WSARecv(data->cliSock, &data->wsabuf, 1, &data->nBytes, &dwFlags, &data->Overlapped, NULL);
	if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
	{
		printf("WASRecv Failed:%s\n", WSAGetLastError());
		closesocket(data->cliSock);
		free(data->wsabuf.buf);
		free(data);
		return FALSE;
	}
	return TRUE;
}

BOOL PostWrite(IoData *data, DWORD nSendLen)
{
	memset(&data->Overlapped, 0, sizeof(data->Overlapped));
	data->nBytes = nSendLen;
	data->opCode = IoWRITE;
	int nRet = WSASend(data->cliSock, &data->wsabuf, 1, &data->nBytes, 0, &(data->Overlapped), NULL);
	if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
	{
		printf("WASSend Failed:%s", WSAGetLastError());
		closesocket(data->cliSock);
		free(data->wsabuf.buf);
		free(data);
		return FALSE;
	}
	return TRUE;
}

DWORD WINAPI WorkerThread(HANDLE hIOCP)
{
	IoData *ctx = NULL;
	DWORD dwIoSize = 0;
	void *lpCompletionKey = NULL;
	LPOVERLAPPED lpOverlapped = NULL;

	while (1)
	{
		GetQueuedCompletionStatus(hIOCP, &dwIoSize, (PULONG_PTR)&lpCompletionKey, (LPOVERLAPPED *)&lpOverlapped, INFINITE);
		ctx = (IoData *)lpOverlapped;
		if (dwIoSize == 0)
		{
			if (ctx == NULL)
			{
				printf("WorkerThread Exit...\n");
				break;
			}
			printf("Client:%d disconnect\n", ctx->cliSock);
			closesocket(ctx->cliSock);
			free(ctx->wsabuf.buf);
			free(ctx);
			continue;
		}
		if (ctx->opCode == IoREAD)
		{
			ctx->wsabuf.buf[dwIoSize] = 0;
			printf("%s\n", ctx->wsabuf.buf);
			PostWrite(ctx, dwIoSize);
		}
		else if (ctx->opCode == IoWRITE)
		{
			PostRead(ctx);
		}
	}
	return 0;
}

static BOOL IsExit = FALSE;

void OnSignal(int sig)
{
	IsExit = TRUE;
	printf("Recv exit signal...\n");
}

void SetNonblocking(int fd)
{
	unsigned long ul = 1;
	int ret = ioctlsocket(fd, FIONBIO, &ul);
	if (ret == SOCKET_ERROR)
	{
		printf("set socket:%d non blocking failed:%s\n", WSAGetLastError());
	}
}

void NetWork(int port)
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	SOCKET m_socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	bind(m_socket, (struct sockaddr *)&server, sizeof(server));
	listen(m_socket, 0);

	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	int threadCount = sysInfo.dwNumberOfProcessors;

	HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadCount);
	for (int i = 0; i < threadCount; ++i)
	{
		HANDLE hThread;
		DWORD dwThreadId;
		hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, &dwThreadId);
		CloseHandle(hThread);
	}
	SetNonblocking(m_socket);
	while (!IsExit)
	{
		SOCKET cliSock = accept(m_socket, NULL, NULL);
		if (cliSock == SOCKET_ERROR)
		{
			Sleep(10);
			continue;
		}
		printf("Client:%d connected.\n", cliSock);
		if (CreateIoCompletionPort((HANDLE)cliSock, hIOCP, 0, 0) == NULL)
		{
			printf("Binding Client Socket to IO Completion Port Failed:%s\n", GetLastError());
			closesocket(cliSock);
		}
		else
		{
			IoData *data = (IoData *)malloc(sizeof(IoData));
			memset(data, 0, sizeof(IoData));
			data->wsabuf.buf = (char*)malloc(1024);
			data->wsabuf.len = 1024;
			data->cliSock = cliSock;
			PostRead(data);
		}
	}
	PostQueuedCompletionStatus(hIOCP, 0, 0, 0);
	closesocket(m_socket);
	WSACleanup();
}

int main()
{
	SetConsoleOutputCP(65001);
	signal(SIGINT, OnSignal);
	NetWork(6000);
	printf("exit\n");
	return 0;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
project(t VERSION 0.1.0)

include(CTest)
enable_testing()

add_executable(server server.c)
add_executable(client client.c)

target_link_libraries(server ws2_32)
target_link_libraries(client ws2_32)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

以上源码在MinGW下编译运行成功。

如果想要添加MiniDump可以添加前文的MiniDump不生成或者生成0字节的MiniDump.c

代码中调用InitMiniDump(),并修改CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
project(t VERSION 0.1.0)

include(CTest)
enable_testing()

add_executable(server server.c MiniDump.c)
add_executable(client client.c)

target_link_libraries(server ws2_32 dbghelp)
target_link_libraries(client ws2_32)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

# 如果不是使用的MS VC编译器,则需要自行生成PDB文件,方便VS调试Dump文件
# 这里使用cv2pdb(https://github.com/rainers/cv2pdb)工具来生成,需要自行下载,
if(NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
add_custom_command(TARGET server POST_BUILD
	COMMAND ../cv2pdb64 server.exe
	DEPENDS server.exe
)
endif()

在CMakeLists.txt中有一个判断,如果不是使用的MS VC编译器,则需要自行生成PDB文件,方便VS调试Dump文件,使用cv2pdb工具来生成,需要自行下载。

需要注意的是,服务器端,如果有连接时,关闭服务器会有内存泄漏,即IoData及其消息Buffer没有释放。

你可能感兴趣的:(#,C/C++,Windows,网络,windows,网络,mingw,iocp,epoll)