聊天室-IOCP服务器

main

  1. 创建完成端口内核对象(CreateIoCompletionPort)

  2. 获取核心数并创建线程(GetSystemInfo + CreateThread)

  3. 创建套接字并绑定接听(socket + bind + listen)

  4. 接收客户端并绑定IOCP(accept + CreateIoCompletionPort)

  5. 将客户端套接字添加到队列(vector.pushback)

  6. 投递一个客户端套接字的IO请求(WSARecv)

thread

  1. 从完成队列中获取完成的消息(GetQueuedCompletionStatus)

  2. 判断是否获取成功,如果成功就转发消息,投递一个新的IO请求

  3. 如果失败就将自己从客户端容器中删除

#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\n", ErrMsg);
system("pause"); ExitProcess();
}
} // 用于执行接收数据的线程
DWORD WINAPI ThreadRoutine(LPVOID lpThreadParameter)
{
// 保存函数的执行结果
BOOL Result = FALSE; // 保存实际的操作数量
DWORD RealOper = ; // 定义完成键,作用是区分IO请求是谁发送的,在绑定的时候设置
ULONG_PTR CompletKey = ; // 重叠 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 == )
{
for (int i = ; i < ClientList.size(); ++i)
{
// 找到当前对应的套接字
if (ClientList[i] == (SOCKET)CompletKey)
{
// 收尾工作
closesocket((SOCKET)CompletKey);
printf("%08X 退出了聊天室\n", (SOCKET)CompletKey);
ClientList.erase(ClientList.begin() + i);
break;
}
}
}
else
{
// 转发给当前在线的除自己以外的所有客户端
for (size_t i = ; i < ClientList.size(); ++i)
{
// 排除掉自己,不会发消息给自己
if (ClientList[i] == (SOCKET)CompletKey)
continue; // 直接转发消息
send(ClientList[i], pOverLapped->WsaBuf.buf, pOverLapped->WsaBuf.len, );
} // 释放堆空间的数据
delete[] pOverLapped->WsaBuf.buf;
delete pOverLapped; // 每一个异步 IO 操作都需要重叠结构
MyOverLapped* OverLapped = new MyOverLapped{ };
OverLapped->WsaBuf.len = 0x100;
OverLapped->WsaBuf.buf = new char[0x100]{ }; // 先投递一个接收的消息
DWORD RealRecv = , Flags = ;
WSARecv(
(SOCKET)CompletKey, // 客户端套接字
&OverLapped->WsaBuf, // 保存缓冲区和缓冲区的大小
, // 缓冲区的个数
&RealRecv, // 实际操作数
&Flags, // 标志
(OVERLAPPED*)OverLapped,// 重叠结构
NULL); // 回调函数
}
} return ;
} int main()
{
// [1] 创建一个完成端口内核对象,固定写法
HANDLE IoHanlde = CreateIoCompletionPort(
INVALID_HANDLE_VALUE, NULL, , ); // [2] 获取当前 CPU 的数量
SYSTEM_INFO SystemInfo = { };
GetSystemInfo(&SystemInfo); // [3] 根据获取的 CPU 数量,创建线程
for (int i = ; i < SystemInfo.dwNumberOfProcessors; ++i)
CreateThread(NULL, NULL, ThreadRoutine, (LPVOID)IoHanlde, NULL, NULL); // 2. 初始化网络环境并判断是否成功[ 搜索信号(2G?3G?4G?) ]
WSAData WsaData = { };
if (!WSAStartup(MAKEWORD(, ), &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 = { };
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 = { }; // 接收的客户端ip和端口
SOCKET ClientSocket = accept(ServerSocket,
(SOCKADDR*)&ClientAddr, &dwSize); // 添加到容器中
printf("%08X 进入了聊天室\n", ClientSocket);
ClientList.push_back(ClientSocket); // [4] 绑定套接字对象到 IOCP 句柄
CreateIoCompletionPort((HANDLE)ClientSocket,
// (完成键)会在接收到消息时,传入到线程函数中
IoHanlde, (ULONG_PTR)ClientSocket, ); // [5] 每一个异步 IO 操作都需要重叠结构
MyOverLapped* OverLapped = new MyOverLapped{ };
OverLapped->WsaBuf.len = 0x100;
OverLapped->WsaBuf.buf = new char[0x100]{ }; // [6] 先投递一个接收的消息
DWORD RealRecv = , Flags = ;
WSARecv(
ClientSocket, // 客户端套接字
&OverLapped->WsaBuf, // 保存缓冲区和缓冲区的大小
, // 缓冲区的个数
&RealRecv, // 实际操作数
&Flags, // 标志
(OVERLAPPED*)OverLapped,// 重叠结构
NULL); // 回调函数
} // 7. 关闭套接字执行清理工作
closesocket(ServerSocket); // 8. 清理网络环境
WSACleanup(); system("pause");
return ;
}
05-11 18:25