IOCP :输入输出完成端口。
是支持多个同时发生的异步I/O操作的应用程序编程接口,IOCP特别适合C/S模式网络服务器端模型。
因为,让每一个socket有一个线程负责同步(阻塞)数据处理,one-thread-per-client的缺点是:一是如果连入的客户多了,就需要同样多的线程;二是不同的socket的数据处理都要线程切换的代价。
最佳线程数量=cpu内核数量*2
一个IOCP对象,在操作系统中可关联着多个Socket和(或)文件控制端。 IOCP对象内部有一个先进先出(FIFO)队列,用于存放IOCP所关联的输入输出端的服务请求完成消息。请求输入输出服务的进程不接收IO服务完成通知,而是检查IOCP的消息队列以确定IO请求的状态。
IOCP是一个内核对象,但是他是一个不需要安全属性的Windows内核对象
使用IOCP模型,首先必须创建(或者关联)一个IOCP对象(与文件句柄):
HANDLE CreateIoCompletionPort(
[in] HANDLE FileHandle,//要关联的文件句柄
[in, optional] HANDLE ExistingCompletionPort,//完成端口句柄
[in] ULONG_PTR CompletionKey,//完成键
[in] DWORD NumberOfConcurrentThreads//允许的最大线程数
);
CreateIoCompletionPort 函数创建 IOCP完成端口并将其与指定的文件句柄相关联,或者创建一个新的未被关联的IOCP对象。
获取IOCP的消息队列:
BOOL GetQueuedCompletionStatus(
[in] HANDLE CompletionPort,//IOCP对象句柄
LPDWORD lpNumberOfBytesTransferred,//IO操作传输的字节数
[out] PULONG_PTR lpCompletionKey,//发送消息方
[out] LPOVERLAPPED *lpOverlapped,//接受消息,并且存储消息的结构体指针
[in] DWORD dwMilliseconds//接受等待时间
);
GetQueuedCompletionStatus 函数尝试从指定的 I/O 完成端口取消 I/O 完成数据包的排队。
如果没有排队的完成数据包,该函数将等待与完成端口关联的挂起 I/O 操作完成。
说白了就是 接收从指定的发送方发送的消息,并且取消排队。
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
std::vector<SOCKET> Vec_Socket;
typedef struct My_Overlapped
{
OVERLAPPED wsaoverlapped;
WSABUF wsabuf;
}My_Overlapped,*PMy_Overlapped;
DWORD WINAPI DoMessage(
LPVOID lpParameter
)
{
HANDLE SocketHandle = (HANDLE)lpParameter;
BOOL Result = FALSE;
PMy_Overlapped myOverlapped = nullptr;
DWORD NumOfReadISze = 0;
ULONG_PTR ComplixKey = 0;
DWORD Flags = 0;
//指向接收与 I / O 操作已完成的文件句柄关联的完成键值的变量的指针
ULONG_PTR client{ 0 };
while (true)
{
//等待消息的读入
Result = GetQueuedCompletionStatus(
SocketHandle,
&NumOfReadISze,
&client, //从每一个客户句柄里读取信息
(LPOVERLAPPED*)&myOverlapped,
INFINITE
);
if (Result && NumOfReadISze > 0)
{
for (UINT i = 0; i < Vec_Socket.size(); i++)
{
//发送消息
if (Vec_Socket[i] != client)
{//句柄不是你自己,往其他人客户端发送消息
send(Vec_Socket[i], myOverlapped->wsabuf.buf, myOverlapped->wsabuf.len, NULL);
}
}
//清空缓冲区
memset(myOverlapped->wsabuf.buf, 0, 0x100);
memset(&myOverlapped->wsaoverlapped, 0, sizeof(OVERLAPPED));
WSARecv(
client,
&myOverlapped->wsabuf,
1,
&NumOfReadISze,
&Flags,
(LPWSAOVERLAPPED)myOverlapped,
NULL
);
}
else
{
//卸载客户端
for (UINT i = 0; i < Vec_Socket.size(); i++)
{
if (Vec_Socket[i] == client)
{
closesocket(client);
printf("客户端%u断开了连接\n", Vec_Socket[i]);
Vec_Socket.erase(Vec_Socket.begin() + i);
}
}
}
}
return 1;
}
int main()
{
// 创建IOCP对象
HANDLE IOCP = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
NULL,
NULL
);
if (!IOCP)
{
printf("IOCP创建失败!\n");
return 0;
}
//获取CPU的内核处理器数量
SYSTEM_INFO system_info{ 0 };
GetNativeSystemInfo(&system_info);
for (UINT i = 0; i < system_info.dwNumberOfProcessors*2; i++)
{
CreateThread(
NULL,
NULL,
DoMessage,
(LPVOID)IOCP, //IOCP传入
NULL,
NULL
);
}
//1. 初始化网络环境
WSADATA WsaData{ 0 };
WSAStartup(
MAKEWORD(2, 2), //套接字
&WsaData
);
//2. 创建socket套接字
//创建绑定到特定传输服务提供者的套接字。
SOCKET serve = socket(
AF_INET,
SOCK_STREAM,
IPPROTO_TCP
);
//3. 绑定端口号IP地址
sockaddr_in serverAddr{ 0 };
serverAddr.sin_family = AF_INET; //地址族
serverAddr.sin_port = htons(666); //端口号
//inet_pton:标准文本呈现形式中将 IPv4 或 IPv6 Internet 网络地址转换为其数字二进制形式
inet_pton(AF_INET, "172.20.230.126", &serverAddr.sin_addr);
//bind:绑定函数将本地地址与套接字相关联。
bind(serve, (sockaddr*)&serverAddr, sizeof(serverAddr));
//4. 监听
//侦听函数将套接字置于侦听传入连接的状态。
listen(serve, SOMAXCONN);
sockaddr_in ClientAddr{ 0 };
int SockSize = sizeof(ClientAddr);
//5. 接收会话
while (1)
{
//连接客户端与服务器,返回客户端句柄
SOCKET client = accept(
serve, //标识已使用监听函数处于侦听状态的套接字
(sockaddr*)&ClientAddr,
&SockSize
);
//保存所有的客户端句柄于一个容器中
Vec_Socket.push_back(client);
//将SOCKET关联到IOCP
CreateIoCompletionPort(
(HANDLE)client,
IOCP,
client,
NULL
);
PMy_Overlapped MyOverlapped = new My_Overlapped{ 0 };
MyOverlapped->wsabuf.buf = new char[0x100] {0};
MyOverlapped->wsabuf.len = 0x100; //缓冲区的长度
DWORD RealSize = 0;
DWORD Flags = 0;
//接收消息
WSARecv(
client,
&MyOverlapped->wsabuf,
1,
&RealSize,
&Flags,
(LPWSAOVERLAPPED)MyOverlapped,
NULL
);
printf("服务器%u连接到了客户端\n", client);
}
closesocket(serve);
system("pause");
return 0;
}
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
DWORD WINAPI GetMessageAAA(
LPVOID lpParameter
)
{
SOCKET Socket = (SOCKET)lpParameter;
CHAR DstBuff[0x100]{ 0 };
while (recv(Socket, DstBuff, 0x100, NULL) > 0)
{
printf("接收:%s\n", DstBuff);
}
return 1;
}
int main()
{
//1. 初始化网络连接
WSADATA WsaData{ 0 };
WSAStartup(MAKEWORD(2, 2), &WsaData);
//2. 创建SOCKET套接字
SOCKET serve = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//3. 绑定IP地址
sockaddr_in serveAddr{ 0 };
serveAddr.sin_family = AF_INET; //地址族
serveAddr.sin_port = htons(666); //端口号
inet_pton(AF_INET, "172.20.230.126", &serveAddr.sin_addr);
//连接服务器
//connect函数建立与指定套接字的连接。
int result = connect(serve, (sockaddr*)&serveAddr, sizeof(serveAddr));
if (result != 0) //未发生错误,返回零
{
printf("连接失败!\n");
system("pause");
return 0;
}
//创建线程
HANDLE Thread = CreateThread(
NULL,
NULL,
GetMessageAAA,
(LPVOID)serve, //传入当前的客户端句柄
NULL,
NULL
);
CHAR lpBuff[0x100]{ 0 };
while (scanf("%s", lpBuff) && strcmp(lpBuff, "exit"))
{
send(serve, lpBuff, strlen(lpBuff)+1, NULL);
}
system("pause");
closesocket(serve);
return 0;
}
运行效果: