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