-
创建完成端口内核对象(CreateIoCompletionPort)
-
获取核心数并创建线程(GetSystemInfo + CreateThread)
-
创建套接字并绑定接听(socket + bind + listen)
-
接收客户端并绑定IOCP(accept + CreateIoCompletionPort)
-
将客户端套接字添加到队列(vector.pushback)
-
投递一个客户端套接字的IO请求(WSARecv)
thread
-
从完成队列中获取完成的消息(GetQueuedCompletionStatus)
-
判断是否获取成功,如果成功就转发消息,投递一个新的IO请求
-
如果失败就将自己从客户端容器中删除
#include <stdio.h> // 1. 包含必要的头文件和库, 必须位于 windows之前 #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") #include <windows.h> #include <ws2tcpip.h> // 动态数组 #include <vector> using namespace std; // 用于保存所有连接的客户端 vector<SOCKET> ClientList; // 创建一个自定义的 OVERLAPPED 的结构,附加一些数据 struct MyOverLapped { // 保留原始的重叠结构 OVERLAPPED Overlapped; // 自己添加新的数据 WSABUF WsaBuf; }; // 工具函数,用于判断是否执行成功 VOID CheckResult(BOOL Value, LPCWSTR ErrMsg) { // 如果 Value 非空,就表示执行成功 if (Value == FALSE) { printf("ErrMsg: %ls ", ErrMsg); system("pause"); ExitProcess(0); } } // 用于执行接收数据的线程 DWORD WINAPI ThreadRoutine(LPVOID lpThreadParameter) { // 保存函数的执行结果 BOOL Result = FALSE; // 保存实际的操作数量 DWORD RealOper = 0; // 定义完成键,作用是区分IO请求是谁发送的,在绑定的时候设置 ULONG_PTR CompletKey = 0; // 重叠 IO 结构 MyOverLapped* pOverLapped = nullptr; // 获取 IOCP 内核对象 HANDLE IoHandle = (HANDLE)lpThreadParameter; // 2. 在循环中不断的接收数据,如果返回值为 > 0 表示成功 while (TRUE) { // 当有 IO 请求完成时,从中获取完成的信息,没有就休眠 Result = GetQueuedCompletionStatus( IoHandle, // IOCP 内核对象 &RealOper, // 实际操作数量 &CompletKey, // 完成键 (LPOVERLAPPED*)& pOverLapped, // 存放重叠结构 INFINITE); // 等待时长 // 3. 将当前的套接字关闭并从列表移除 if (Result == FALSE && RealOper == 0) { for (int i = 0; i < ClientList.size(); ++i) { // 找到当前对应的套接字 if (ClientList[i] == (SOCKET)CompletKey) { // 收尾工作 closesocket((SOCKET)CompletKey); printf("%08X 退出了聊天室 ", (SOCKET)CompletKey); ClientList.erase(ClientList.begin() + i); break; } } } else { // 转发给当前在线的除自己以外的所有客户端 for (size_t i = 0; i < ClientList.size(); ++i) { // 排除掉自己,不会发消息给自己 if (ClientList[i] == (SOCKET)CompletKey) continue; // 直接转发消息 send(ClientList[i], pOverLapped->WsaBuf.buf, pOverLapped->WsaBuf.len, 0); } // 释放堆空间的数据 delete[] pOverLapped->WsaBuf.buf; delete pOverLapped; // 每一个异步 IO 操作都需要重叠结构 MyOverLapped* OverLapped = new MyOverLapped{ 0 }; OverLapped->WsaBuf.len = 0x100; OverLapped->WsaBuf.buf = new char[0x100]{ 0 }; // 先投递一个接收的消息 DWORD RealRecv = 0, Flags = 0; WSARecv( (SOCKET)CompletKey, // 客户端套接字 &OverLapped->WsaBuf, // 保存缓冲区和缓冲区的大小 1, // 缓冲区的个数 &RealRecv, // 实际操作数 &Flags, // 标志 (OVERLAPPED*)OverLapped,// 重叠结构 NULL); // 回调函数 } } return 0; } int main() { // [1] 创建一个完成端口内核对象,固定写法 HANDLE IoHanlde = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, 0, 0); // [2] 获取当前 CPU 的数量 SYSTEM_INFO SystemInfo = { 0 }; GetSystemInfo(&SystemInfo); // [3] 根据获取的 CPU 数量,创建线程 for (int i = 0; i < SystemInfo.dwNumberOfProcessors; ++i) CreateThread(NULL, NULL, ThreadRoutine, (LPVOID)IoHanlde, NULL, NULL); // 2. 初始化网络环境并判断是否成功[ 搜索信号(2G?3G?4G?) ] WSAData WsaData = { 0 }; if (!WSAStartup(MAKEWORD(2, 2), &WsaData)) CheckResult(WsaData.wVersion == 0x0202, L"初始化网络环境失败"); // 3. 创建套接字(IP+PORT) [ 买手机 ] SOCKET ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); CheckResult(ServerSocket != INVALID_SOCKET, L"套接字创建失败"); // 4. 绑定套接字,提供IP和端口 (办手机卡) sockaddr_in ServerAddr = { 0 }; ServerAddr.sin_port = htons(0x1515); // 端口 ServerAddr.sin_family = AF_INET; // 协议类型 inet_pton(AF_INET, "127.0.0.1", &ServerAddr.sin_addr.S_un); BOOL Result = bind(ServerSocket, // 要绑定的套接字 (SOCKADDR*)& ServerAddr, // 服务器的地址和IP对应的结构体 sizeof(sockaddr_in)); // 必须要指定大小 CheckResult(Result != SOCKET_ERROR, L"套接字绑定失败"); // 5. 监听套接字 (开机,等待连接) // - 监听谁,最多等待多少个客户端的链接 Result = listen(ServerSocket, SOMAXCONN); // 6. 循环等待客户端的连接(接电话) while (true) { // 接收客户端 int dwSize = sizeof(sockaddr_in); sockaddr_in ClientAddr = { 0 }; // 接收的客户端ip和端口 SOCKET ClientSocket = accept(ServerSocket, (SOCKADDR*)&ClientAddr, &dwSize); // 添加到容器中 printf("%08X 进入了聊天室 ", ClientSocket); ClientList.push_back(ClientSocket); // [4] 绑定套接字对象到 IOCP 句柄 CreateIoCompletionPort((HANDLE)ClientSocket, // (完成键)会在接收到消息时,传入到线程函数中 IoHanlde, (ULONG_PTR)ClientSocket, 0); // [5] 每一个异步 IO 操作都需要重叠结构 MyOverLapped* OverLapped = new MyOverLapped{ 0 }; OverLapped->WsaBuf.len = 0x100; OverLapped->WsaBuf.buf = new char[0x100]{ 0 }; // [6] 先投递一个接收的消息 DWORD RealRecv = 0, Flags = 0; WSARecv( ClientSocket, // 客户端套接字 &OverLapped->WsaBuf, // 保存缓冲区和缓冲区的大小 1, // 缓冲区的个数 &RealRecv, // 实际操作数 &Flags, // 标志 (OVERLAPPED*)OverLapped,// 重叠结构 NULL); // 回调函数 } // 7. 关闭套接字执行清理工作 closesocket(ServerSocket); // 8. 清理网络环境 WSACleanup(); system("pause"); return 0; }