/* 总结iocp的几个步骤(注:完成键是在关联iocp的时候传入,重叠体是在投递任务的时候传入) 1.创建空完成端口 createIoCompletionPort() 2.关联完成端口和server套接字createIoCompletionPort() 3.向完成端口投递接收连接任务 accepEx() 完成端口是投递一个任务 处理一个 4.检测完成端口状态getCueuedCompletionStatus() 5.在接收连接完成分支投递接收任务WSARecv() 6.在接收数据完成分支投递发送任务WSASend() 7.在发送完成分支投递接收数据任务WSARecv() */
#define WIN32_LEAN_AND_MEAN
#include<windows.h> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib")
#include<mswsock.h> #pragma comment(lib,"Mswsock.lib")
#include<stdio.h>
#define nClient 10 enum IO_TYPE { ?? ?ACCEPT = 10, ?? ?RECV, ?? ?SEND };
//数据缓冲区空间大小 #define DATA_BUFF_SIZE 1024 struct IO_DATA_BASE { ?? ?//重叠体 ?? ?OVERLAPPED overlapped; ?? ?// ?? ?SOCKET sockfd; ?? ?//数据缓冲区 ?? ?char buffer[DATA_BUFF_SIZE]; ?? ?//实际缓冲区数据长度 ?? ?int length; ?? ?//操作类型 ?? ?IO_TYPE iotype; }; //向IOCP投递接受连接的任务 void postAccept(SOCKET sockServer, IO_DATA_BASE* pIO_DATA) { ?? ?pIO_DATA->iotype = IO_TYPE::ACCEPT; ?? ?pIO_DATA->sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); ?? ?if (FALSE == AcceptEx(sockServer ?? ??? ?, pIO_DATA->sockfd ?? ??? ?, pIO_DATA->buffer ?? ??? ?, 0 ?? ??? ?, sizeof(sockaddr_in) + 16 ?? ??? ?, sizeof(sockaddr_in) + 16 ?? ??? ?, NULL ?? ??? ?, &pIO_DATA->overlapped ?? ?)) ?? ?{ ?? ??? ?int err = WSAGetLastError(); ?? ??? ?if (ERROR_IO_PENDING != err) ?? ??? ?{ ?? ??? ??? ?printf("AcceptEx failed with error %d\n", err); ?? ??? ??? ?return; ?? ??? ?} ?? ?} } //向IOCP投递接收数据的任务 void postRecv(IO_DATA_BASE* pIO_DATA) { ?? ?pIO_DATA->iotype = IO_TYPE::RECV; ?? ?WSABUF wsBuff = {}; ?? ?wsBuff.buf = pIO_DATA->buffer;//可以指向我们自定义的buffer,方便后续做粘包处理 ?? ?wsBuff.len = DATA_BUFF_SIZE; ?? ?DWORD flags = 0; ?? ?ZeroMemory(&pIO_DATA->overlapped, sizeof(OVERLAPPED));
?? ?if (SOCKET_ERROR == WSARecv(pIO_DATA->sockfd, &wsBuff, 1, NULL, &flags, &pIO_DATA->overlapped, NULL)) ?? ?{ ?? ??? ?int err = WSAGetLastError(); ?? ??? ?if (ERROR_IO_PENDING != err) ?? ??? ?{ ?? ??? ??? ?printf("WSARecv failed with error %d\n", err); ?? ??? ??? ?return; ?? ??? ?} ?? ?} } //向IOCP投递发送数据的任务 void postSend(IO_DATA_BASE* pIO_DATA) { ?? ?pIO_DATA->iotype = IO_TYPE::SEND; ?? ?WSABUF wsBuff = {}; ?? ?wsBuff.buf = pIO_DATA->buffer; ?? ?wsBuff.len = pIO_DATA->length; ?? ?DWORD flags = 0; ?? ?ZeroMemory(&pIO_DATA->overlapped, sizeof(OVERLAPPED));
?? ?if (SOCKET_ERROR == WSASend(pIO_DATA->sockfd, &wsBuff, 1, NULL, flags, &pIO_DATA->overlapped, NULL)) ?? ?{ ?? ??? ?int err = WSAGetLastError(); ?? ??? ?if (ERROR_IO_PENDING != err) ?? ??? ?{ ?? ??? ??? ?printf("WSASend failed with error %d\n", err); ?? ??? ??? ?return; ?? ??? ?} ?? ?} } //-- 用Socket API建立简易TCP服务端 //-- IOCP Server基础流程 int main() { ?? ?//启动Windows socket 2.x环境 ?? ?WORD ver = MAKEWORD(2, 2); ?? ?WSADATA dat; ?? ?WSAStartup(ver, &dat); ?? ?//------------// ?? ?// 1 建立一个socket ?? ?// 当使用socket函数创建套接字时,会默认设置WSA_FLAG_OVERLAPPED标志 ?? ?// ?? ?// 注意这里也可以用 WSASocket函数创建socket ?? ?// 最后一个参数需要设置为重叠标志(WSA_FLAG_OVERLAPPED) ?? ?// SOCKET sockServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); ?? ?// ?? ?SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
?? ?// 2.1 设置对外IP与端口信息? ?? ?sockaddr_in _sin = {}; ?? ?_sin.sin_family = AF_INET; ?? ?_sin.sin_port = htons(4567);//host to net unsigned short ?? ?_sin.sin_addr.s_addr = INADDR_ANY; ?? ?// 2.2 绑定sockaddr与ServerSocket ?? ?if (SOCKET_ERROR == bind(sockServer, (sockaddr*)&_sin, sizeof(_sin))) ?? ?{ ?? ??? ?printf("错误,绑定网络端口失败...\n"); ?? ?} ?? ?else { ?? ??? ?printf("绑定网络端口成功...\n"); ?? ?}
?? ?// 3 监听ServerSocket ?? ?if (SOCKET_ERROR == listen(sockServer, 64)) ?? ?{ ?? ??? ?printf("错误,监听网络端口失败...\n"); ?? ?} ?? ?else { ?? ??? ?printf("监听网络端口成功...\n"); ?? ?} ?? ?//-------IOCP Begin-------//
?? ?//4 创建完成端口 第三个参数:完成键(自定义数据结构)最后一个参数0 默认允许并发线程数量 ?? ?HANDLE _completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); ?? ?if (NULL == _completionPort) ?? ?{ ?? ??? ?printf("CreateIoCompletionPort failed with error %d\n", GetLastError()); ?? ??? ?return -1; ?? ?} ?? ?//5 关联iocp和serversocket 第三个参数是完成键用于在关联套接字的时候传递参数
?? ?auto ret = CreateIoCompletionPort((HANDLE)sockServer, _completionPort, (ULONG_PTR)sockServer, 0); ?? ?if (!ret) ?? ?{ ?? ??? ?printf("关联IOCP与ServerSocket失败,错误码=%d\n", GetLastError()); ?? ??? ?return -1; ?? ?}
?? ?//6 向IOCP投递接受连接的任务 ?? ?//_In_ SOCKET sListenSocket,?? ??? ?监听socket ?? ?//_In_ SOCKET sAcceptSocket,?? ??? ?主动为客户端创建socket(需要自己提前创建好) ?? ?//_Out_writes_bytes_(dwReceiveDataLength+dwLocalAddressLength+dwRemoteAddressLength) PVOID lpOutputBuffer,?? ??? ? ?? ?//_In_ DWORD dwReceiveDataLength,?? ?接收数据的长度(这个地方只要传入一个非零值,我们服务端都要求客户端必须发送一条数据上来才能完成连接,所以这里传0) ?? ?//_In_ DWORD dwLocalAddressLength,?? ?本地网络地址的长度(规定该值必须至少比正在使用的传输协议的最大地址长度多16字节) ?? ?//_In_ DWORD dwRemoteAddressLength,?? ?远端网络地址的长度 (规定该值必须至少比正在使用的传输协议的最大地址长度多16字节) ?? ?//_Out_ LPDWORD lpdwBytesReceived,?? ?实际接收到的数据长度--iocp这里没有意义不用设置该值 这个函数直接返回的? ?? ?//_Inout_ LPOVERLAPPED lpOverlapped 重叠体IOCP需要使用 ?? ?IO_DATA_BASE ioData[nClient] = {}; ?? ?for (int n = 0; n < nClient; n++) ?? ?{ ?? ??? ?postAccept(sockServer, &ioData[n]); ?? ?}
?? ?int msgCount = 0; ?? ?while (true) ?? ?{
?? ??? ?//_In_ HANDLE CompletionPort,?? ??? ??? ??? ?创建的完成端口 ?? ??? ?//_Out_ LPDWORD lpNumberOfBytesTransferred, 接收到或者发送的数据字节数 ?? ??? ?//_Out_ PULONG_PTR lpCompletionKey,?? ??? ??? ?完成键 参数回传关联iocp和sockserver时候传入的 ?? ??? ?//_Out_ LPOVERLAPPED* lpOverlapped,?? ??? ??? ?在向iocp投递任务传递 在 GetQueuedCompletionStatus返回 ?? ??? ?//_In_ DWORD dwMilliseconds?? ??? ??? ??? ??? ?超时时间ms单位 ?? ??? ?DWORD bytesTrans = 0;?? ??? ??? ??? ??? ??? ?//实际接收和发送的数据 ?? ??? ?SOCKET sock = INVALID_SOCKET; ?? ??? ?IO_DATA_BASE* pIOData;
?? ??? ?if (FALSE == GetQueuedCompletionStatus(_completionPort, &bytesTrans, (PULONG_PTR)&sock, (LPOVERLAPPED*)&pIOData, 1)) ?? ??? ?{ ?? ??? ??? ?int err = GetLastError(); ?? ??? ??? ?if (WAIT_TIMEOUT == err) ?? ??? ??? ?{ ? ?? ??? ??? ??? ?continue; ?? ??? ??? ?} ?? ??? ??? ?if (ERROR_NETNAME_DELETED == err) ?? ??? ??? ?{ ?? ??? ??? ??? ?printf("关闭 sockfd=%d\n", pIOData->sockfd); ?? ??? ??? ??? ?closesocket(pIOData->sockfd); ?? ??? ??? ??? ?continue; ?? ??? ??? ?} ?? ??? ??? ?printf("GetQueuedCompletionStatus failed with error %d\n", err); ?? ??? ??? ?break; ?? ??? ?} ?? ??? ?//7.1 接受链接 完成 ?? ??? ?if (IO_TYPE::ACCEPT == pIOData->iotype) ?? ??? ?{ ?? ??? ??? ?printf("新客户端加入 sockfd=%d\n", pIOData->sockfd); ?? ??? ??? ?//7.2 关联IOCP与ClientSocket ?? ??? ??? ?auto ret = CreateIoCompletionPort((HANDLE)pIOData->sockfd, _completionPort, (ULONG_PTR)pIOData->sockfd, 0); ?? ??? ??? ?if (!ret) ?? ??? ??? ?{ ?? ??? ??? ??? ?printf("关联IOCP与ClientSocket=%d失败\n", pIOData->sockfd); ?? ??? ??? ??? ?closesocket(pIOData->sockfd); ?? ??? ??? ??? ?continue; ?? ??? ??? ?} ?? ??? ??? ?//7.3 向IOCP投递接收数据任务 ?? ??? ??? ?postRecv(pIOData); ?? ??? ?} ?? ??? ?//8.1 接收数据 完成 Completion ?? ??? ?else if (IO_TYPE::RECV == pIOData->iotype) ?? ??? ?{ ?? ??? ??? ?if (bytesTrans <= 0) ?? ??? ??? ?{//客户端断开处理 ?? ??? ??? ??? ?printf("关闭 sockfd=%d, RECV bytesTrans=%d\n", pIOData->sockfd, bytesTrans); ?? ??? ??? ??? ?closesocket(pIOData->sockfd); ?? ??? ??? ??? ?continue; ?? ??? ??? ?} ?? ??? ??? ?printf("收到数据: sockfd=%d, bytesTrans=%d msgCount=%d\n", pIOData->sockfd, bytesTrans, ++msgCount); ?? ??? ??? ?pIOData->length = bytesTrans; ?? ??? ??? ?//8.2 向IOCP投递发送数据任务 ?? ??? ??? ?postSend(pIOData); ?? ??? ?} ?? ??? ?//9.1 发送数据 完成 Completion ?? ??? ?else if (IO_TYPE::SEND == pIOData->iotype) ?? ??? ?{ ?? ??? ??? ?if (bytesTrans <= 0) ?? ??? ??? ?{//客户端断开处理 ?? ??? ??? ??? ?printf("关闭 sockfd=%d, SEND bytesTrans=%d\n", pIOData->sockfd, bytesTrans); ?? ??? ??? ??? ?closesocket(pIOData->sockfd); ?? ??? ??? ??? ?continue; ?? ??? ??? ?} ?? ??? ??? ?printf("发送数据: sockfd=%d, bytesTrans=%d msgCount=%d\n", pIOData->sockfd, bytesTrans, msgCount); ?? ??? ??? ?//9.2 向IOCP投递接收数据任务 ?? ??? ??? ?postRecv(pIOData); ?? ??? ?} ?? ??? ?else { ?? ??? ??? ?printf("未定义行为 sockfd=%d", sock); ?? ??? ?} ?? ?}
?? ?//------------// ?? ?//10.1 关闭ClientSocket ?? ?for (int n = 0; n < nClient; n++) ?? ?{ ?? ??? ?closesocket(ioData[n].sockfd); ?? ?} ?? ?//10.2 关闭ServerSocket ?? ?closesocket(sockServer); ?? ?//10.3 关闭完成端口 ?? ?CloseHandle(_completionPort); ?? ?//清除Windows socket环境 ?? ?WSACleanup(); ?? ?return 0; }
|