IOCP即完成端口(I/O Completion Port),与Linux下的epoll一样,是一种非常高效的网络模型。epoll 是当资源准备就绪时发出可处理通知消息;IOCP 则是当事件完成时发出完成通知消息。
epoll模型就好比去银行办事:
1.到银行排队取号
2.等待期间,可以忙点其它的事情
3.号到了,通知你去窗口办理(需要你自己去办理,有一个办理过程)
4.办理完后离开
IOCP模型就好比去打印资料:
1.到打印店,把资料给老板进行排队处理
2.等待期间,可以忙点其它的事情
3.打印好了,通知你去拿(不需要你自己去打印,老板已经帮你打印好了)
4.办理完后离开
通过类比描述,可以看出,IOCP更先进一些,下面来看看IOCP的实例:
client.c:
#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSADATA wsaData;
int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (res != 0)
{
printf("Init Network failed:%d", res);
return 1;
}
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 == -1)
{
printf("disconnect, exit");
break;
}
if (rev == 0)
{
printf("Recv Nothing\n");
}
else
{
printf("Recv: %s\n", buffer);
}
Sleep(5000);
}
closesocket(client);
WSACleanup();
return 0;
}
使用accept
函数的server.c
:
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#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;
void CloseConn(IoData* ctx)
{
closesocket(ctx->cliSock);
free(ctx->wsabuf.buf);
free(ctx);
}
BOOL PostRead(IoData* data)
{
memset(&data->Overlapped, 0, sizeof(data->Overlapped));
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:%d\n", WSAGetLastError());
CloseConn(data);
return FALSE;
}
return TRUE;
}
BOOL PostWrite(IoData* data)
{
memset(&data->Overlapped, 0, sizeof(data->Overlapped));
data->opCode = IoWRITE;
printf("%lld Send %s\n", data->cliSock, data->wsabuf.buf);
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:%d", WSAGetLastError());
CloseConn(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:%lld disconnect\n", ctx->cliSock);
CloseConn(ctx);
continue;
}
if (ctx->opCode == IoREAD)
{
ctx->wsabuf.buf[dwIoSize] = 0;
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(SOCKET fd)
{
// Windows下有两个函数设置套接字为非阻塞,一个是常见的ioctlsocket,另一个是比它更强大的WSAIoctl。
#if 1
unsigned long inBuf = 1; // 如果要启用非阻止模式,则为非零;如果要禁用该模式,则为零。
DWORD lpcbBytesReturned = 0;
WSAOVERLAPPED Overlapped;
ZeroMemory(&Overlapped, sizeof(Overlapped));
if (SOCKET_ERROR == WSAIoctl(fd, FIONBIO, &inBuf, sizeof(inBuf), NULL, 0, &lpcbBytesReturned, &Overlapped, NULL))
{
printf("set socket:%lld non blocking failed:%d\n", fd, WSAGetLastError());
}
#else
unsigned long ul = 1; // 如果要启用非阻止模式,则为非零;如果要禁用该模式,则为零。
int ret = ioctlsocket(fd, FIONBIO, &ul);
if (ret == SOCKET_ERROR)
{
printf("set socket:%lld non blocking failed:%d\n", fd, WSAGetLastError());
}
#endif
}
void NetWork(int port)
{
WSADATA wsaData;
int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (res != 0)
{
printf("Init Network failed:%d", res);
return;
}
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);
if (bind(m_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
{
printf("bind failed:%d", WSAGetLastError());
return;
}
if (listen(m_socket, 0) == SOCKET_ERROR)
{
printf("listen failed:%d", WSAGetLastError());
return;
}
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
DWORD threadCount = sysInfo.dwNumberOfProcessors;
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadCount);
if (hIOCP == NULL)
{
printf("CreateIoCompletionPort failed:%d", GetLastError());
return;
}
for (int i = 0; i < threadCount; ++i)
{
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, &dwThreadId);
if (hThread == NULL)
{
printf("CreateThread failed:%d", GetLastError());
continue;
}
CloseHandle(hThread);
}
// 设置为非阻塞模式后,下面的accept函数将立即返回,不会阻塞;否则accept函数会等待一个新的连接到来时,才会返回。
SetNonblocking(m_socket);
while (!IsExit)
{
SOCKET cliSock = accept(m_socket, NULL, NULL);
if (cliSock == SOCKET_ERROR)
{
Sleep(10);
continue;
}
printf("Client:%lld connected.\n", cliSock);
if (CreateIoCompletionPort((HANDLE)cliSock, hIOCP, 0, 0) == NULL)
{
printf("Binding Client Socket to IO Completion Port Failed:%u\n", GetLastError());
closesocket(cliSock);
}
else
{
IoData* data = (IoData*)malloc(sizeof(IoData));
if (data == NULL)
{
printf("out of memory");
closesocket(cliSock);
continue;
}
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);
typedef void (*SignalHandlerPointer)(int);
SignalHandlerPointer previousHandler = signal(SIGINT, OnSignal);
NetWork(6000);
printf("exit\n");
return 0;
}
Windows下比较常见的还有一种接受网络连接的函数,就是使用AcceptEx
函数或者直接使用它的函数指针:
LPFN_ACCEPTEX lpfnAcceptEx = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes = 0;
// Load the AcceptEx function into memory using WSAIoctl.
// The WSAIoctl function is an extension of the ioctlsocket()
// function that can use overlapped I/O. The function's 3rd
// through 6th parameters are input and output buffers where
// we pass the pointer to our AcceptEx function. This is used
// so that we can call the AcceptEx function directly, rather
// than refer to the Mswsock.lib library.
int iResult = WSAIoctl(listenSocket, SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx, sizeof(GuidAcceptEx),
&lpfnAcceptEx, sizeof(lpfnAcceptEx),
&dwBytes, NULL, NULL);
if (iResult == SOCKET_ERROR)
{
wprintf(L"WSAIoctl failed with error: %u\n", WSAGetLastError());
closesocket(listenSocket);
WSACleanup();
return;
}
使用AcceptEx
函数指针的server_ex.c
:
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <mswsock.h>
#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;
void CloseConn(IoData* ctx)
{
if (ctx->Overlapped.hEvent != NULL)
{
WSACloseEvent(ctx->Overlapped.hEvent);
ctx->Overlapped.hEvent = NULL;
}
closesocket(ctx->cliSock);
free(ctx->wsabuf.buf);
free(ctx);
}
BOOL PostRead(IoData* data)
{
memset(&data->Overlapped, 0, sizeof(data->Overlapped));
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:%d\n", WSAGetLastError());
CloseConn(data);
return FALSE;
}
return TRUE;
}
BOOL PostWrite(IoData* data)
{
memset(&data->Overlapped, 0, sizeof(data->Overlapped));
data->opCode = IoWRITE;
printf("%lld Send %s\n", data->cliSock, data->wsabuf.buf);
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:%d", WSAGetLastError());
CloseConn(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:%lld disconnect\n", ctx->cliSock);
CloseConn(ctx);
continue;
}
if (ctx->opCode == IoREAD)
{
ctx->wsabuf.buf[dwIoSize] = 0;
PostWrite(ctx);
}
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 NetWork(int port)
{
WSADATA wsaData;
int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (res != 0)
{
printf("Init Network failed:%d", res);
return;
}
SOCKET listenSocket = 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);
if (bind(listenSocket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
{
printf("bind failed:%d", WSAGetLastError());
return;
}
if (listen(listenSocket, 5) == SOCKET_ERROR)
{
printf("listen failed:%d", WSAGetLastError());
return;
}
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
DWORD threadCount = sysInfo.dwNumberOfProcessors;
threadCount = 1;
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadCount);
if (hIOCP == NULL)
{
printf("CreateIoCompletionPort failed:%d", GetLastError());
return;
}
for (int i = 0; i < threadCount; ++i)
{
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, &dwThreadId);
if (hThread == NULL)
{
printf("CreateThread failed:%d", GetLastError());
continue;
}
CloseHandle(hThread);
}
LPFN_ACCEPTEX lpfnAcceptEx = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes = 0;
// Load the AcceptEx function into memory using WSAIoctl.
// The WSAIoctl function is an extension of the ioctlsocket()
// function that can use overlapped I/O. The function's 3rd
// through 6th parameters are input and output buffers where
// we pass the pointer to our AcceptEx function. This is used
// so that we can call the AcceptEx function directly, rather
// than refer to the Mswsock.lib library.
int iResult = WSAIoctl(listenSocket, SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidAcceptEx, sizeof(GuidAcceptEx),
&lpfnAcceptEx, sizeof(lpfnAcceptEx),
&dwBytes, NULL, NULL);
if (iResult == SOCKET_ERROR)
{
wprintf(L"WSAIoctl failed with error: %u\n", WSAGetLastError());
closesocket(listenSocket);
WSACleanup();
return;
}
while (!IsExit)
{
IoData* data = (IoData*)malloc(sizeof(IoData));
if (data == NULL)
{
printf("out of memory");
break;
}
SOCKET cliSock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
data->cliSock = cliSock;
data->opCode = IoREAD;
data->wsabuf.len = 1024;
data->wsabuf.buf = malloc(data->wsabuf.len);
if (data->wsabuf.buf == NULL)
{
printf("out of memory");
free(data);
closesocket(cliSock);
break;
}
ZeroMemory(&data->Overlapped, sizeof(data->Overlapped));
data->Overlapped.hEvent = WSACreateEvent();
if (data->Overlapped.hEvent == NULL)
{
printf("WSACreateEvent failed with error: %d\n", WSAGetLastError());
closesocket(cliSock);
free(data->wsabuf.buf);
free(data);
break;
}
BOOL bRetVal = lpfnAcceptEx(listenSocket, cliSock, data->wsabuf.buf,
data->wsabuf.len - ((sizeof(struct sockaddr_in) + 16) * 2),
sizeof(struct sockaddr_in) + 16, sizeof(struct sockaddr_in) + 16,
&dwBytes, &data->Overlapped);
if (!bRetVal)
{
int err = WSAGetLastError();
if (err != ERROR_IO_PENDING)
{
printf("AcceptEx failed with error: %d\n", err);
closesocket(cliSock);
free(data->wsabuf.buf);
free(data);
break;
}
}
DWORD res = WSAWaitForMultipleEvents(1, &data->Overlapped.hEvent, 1, WSA_INFINITE, 0);
if (res == WSA_WAIT_FAILED)
{
printf("AcceptEx failed with error: %d\n", WSAGetLastError());
closesocket(cliSock);
free(data->wsabuf.buf);
free(data);
break;
}
WSAResetEvent(data->Overlapped.hEvent);
printf("Client:%lld connected.\n", cliSock);
DWORD flag = 0;
WSAGetOverlappedResult(cliSock, &data->Overlapped, &dwBytes, 1, &flag);
if (dwBytes == 0)
{
closesocket(cliSock);
free(data->wsabuf.buf);
free(data);
continue;
}
if (CreateIoCompletionPort((HANDLE)cliSock, hIOCP, 0, 0) == NULL)
{
printf("Binding Client Socket to IO Completion Port Failed:%u\n", GetLastError());
closesocket(cliSock);
free(data->wsabuf.buf);
free(data);
continue;
}
else
{
data->wsabuf.buf[dwBytes] = 0;
PostWrite(data);
}
}
PostQueuedCompletionStatus(hIOCP, 0, 0, 0);
closesocket(listenSocket);
WSACleanup();
}
int main()
{
SetConsoleOutputCP(65001);
typedef void (*SignalHandlerPointer)(int);
SignalHandlerPointer previousHandler = 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_ex.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工具来生成,需要自行下载。