一、Background
二、Why try IOCP?

其优势表现为:
解决了"one-thread-per-client"的缺点;
完成端口会充分利用Windows内核来进行I/O的调度;
消除了无谓的线程上下文切换,实现了高并发性能;
(4)在服务线程中,等待完成端口重叠IO操作结果;

1、创建完成端口
其所调用的函数为如下:
HANDLE WINAPI CreateIoCompletionPort(_In_ HANDLE FileHandle, 已经打开的文件句柄或者空句柄,一般是客户端的句柄_In_opt_ HANDLE ExistingCompletionPort, 已经存在的IOCP句柄_In_ ULONG_PTR CompletionKey, 完成键,包含了指定I/O完成包的指定文件_In_ DWORD NumberOfConcurrentThreads 真正并发同时执行最大线程数,一般推介是CPU核心数*2);
需要注意的是该函数还有一个作用:
HANDLE m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );
2、建立Worker线程
一般来说线程的数量设置在CPU*2,这样可以更加充分利用CPU资源。
SYSTEM_INFO msi; 确定处理器的核心数量GetSystemInfo(&msi);for (int i = 0; i < msi.dwNumberOfProcessors*2; i++){HANDLE hThread;DWORD Thread_ID;hThread=CreateThread(NULL, 0, _WorkerThread, 0,0,&Thread_ID);CloseHandle(hThread);}
其中,ServerWorkThread是自行定义的Worker线程的线程函数。
3、创建socket
WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData); //MAKEWORD(2, 2)版本号SOCKET _socket=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);SOCKADDR_IN addrServer;addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//inet_addr("0.0.0.0");addrServer.sin_family = AF_INET;addrServer.sin_port = htons(81);bind(_socket, (SOCKADDR*)&addrServer, sizeof(SOCKADDR)); //绑定listen(_socket, 2);//监听
每当有客户端连入的时候,我们还需调用:
CreateIoCompletionPort()函数,与完成端口进行绑定。
int len = sizeof(SOCKADDR);SOCKADDR_IN addr;SOCKET Clietnt=accept(_socket,(SOCKADDR*)&addr,&len);PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中为这个PerHandleData申请指定大小的内存PerHandleData -> socket = Clietnt;memcpy (&PerHandleData -> ClientAddr, &addr, len);if(CreateIoCompletionPort((HANDLE)Clietnt,m_hIOCompletionPort,(ULONG_PTR)PerHandleData,0)==NULL){cout<<"客户端绑定失败"<<endl;closesocket(Clietnt);}
需要注意的是:把上面的代码放进while循环里,每当得到一个新的socket,继续调用函数进行绑定。
而此时却不是新建立完成端口了,而是把新连入的Socket,与目前的完成端口绑定在一起。
至此,我们就完成了完成端口的设定了,接下来就是实现各种操作了,比如说使用WSARecv()函数来接收数据,发起重叠IO操作。
5、worker线程函数定义
在这个线程函数里,我们需要使用以下函数:
GetQueuedCompletionStatus() 来监控完成端口。
BOOL WINAPI GetQueuedCompletionStatus(__in HANDLE CompletionPort, // 这个就是我们建立的那个唯一的完成端口__out LPDWORD lpNumberOfBytes, //这个是操作完成后返回的字节数__out PULONG_PTR lpCompletionKey, // 这个是返回我们建立完成端口的时候绑定的那个自定义结构体参数__out LPOVERLAPPED *lpOverlapped, // 这个是返回我们在连入Socket的时候一起建立的那个重叠结构__in DWORD dwMilliseconds // 等待完成端口的超时时间,如果线程不需要做其他的事情,那就INFINITE就行了);
不过我们可以看到,该函数里有多个参数是我们需要用来接收值的,因此为了方便,定义这些参数成结构体使用:
struct _IO_DATA{int _Bytes;IO_OPERATION opCode;WSABUF wsabuf;OVERLAPPED m_Overlapped; //必要的,其余看你自己定义char buff[2048];};typedef struct //此结构体用来保存客户端的socket{SOCKET socket; //必要的SOCKADDR_STORAGE ClientAddr;}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;
代码实现如下:
HANDLE g_hIocp=(HANDLE)WordThreadcontext; //线程函数参数——端口DWORD dwIoSize=0;LPPER_HANDLE_DATA lpCompletionKey=NULL;LPOVERLAPPED lpOverlapped=NULL;_IO_DATA* _IO_Context=NULL;GetQueuedCompletionStatus(g_hIocp,&dwIoSize,(PULONG_PTR)&lpCompletionKey,(LPOVERLAPPED*)&lpOverlapped,INFINITE)
实现完后,接着就是打印接收到信息了,然后为下一个重叠调用建立单I/O操作数据。
int WSARecv(SOCKET s, // 当然是投递这个操作的套接字LPWSABUF lpBuffers, // 接收缓冲区// 这里需要一个由WSABUF结构构成的数组DWORD dwBufferCount, // 数组中WSABUF结构的数量,设置为1即可LPDWORD lpNumberOfBytesRecvd, // 如果接收操作立即完成,这里会返回函数调用所接收到的字节数LPDWORD lpFlags, // 说来话长了,我们这里设置为0 即可LPWSAOVERLAPPED lpOverlapped, // 这个Socket对应的重叠结构NULL // 这个参数只有完成例程模式才会用到,// 完成端口中我们设置为NULL即可);

mian函数如下:
int main(){HANDLE m_hIOCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0 );SYSTEM_INFO msi; //确定处理器的核心数量GetSystemInfo(&msi);for (int i = 0; i < msi.dwNumberOfProcessors*2; i++){HANDLE hThread=CreateThread(NULL, 0, _WorkerThread,m_hIOCompletionPort ,0,NULL);CloseHandle(hThread);}WSADATA wsaData;WSAStartup(MAKEWORD(2, 2), &wsaData); //MAKEWORD(2, 2)版本号SOCKET _socket=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);SOCKADDR_IN addrServer;addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//inet_addr("0.0.0.0");addrServer.sin_family = AF_INET;addrServer.sin_port = htons(81);bind(_socket, (SOCKADDR*)&addrServer, sizeof(SOCKADDR)); //绑定listen(_socket, 2);//监听cout<<"等待连接....."<<endl;while(1){int len = sizeof(SOCKADDR);SOCKADDR_IN addr;PER_HANDLE_DATA * PerHandleData = NULL;SOCKET Clietnt=accept(_socket,(SOCKADDR*)&addr,&len);cout<<"客户端连接成功......."<<endl;PerHandleData = (LPPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); // 在堆中为这个PerHandleData申请指定大小的内存PerHandleData -> socket = Clietnt;memcpy (&PerHandleData -> ClientAddr, &addr, len);if(CreateIoCompletionPort((HANDLE)Clietnt,m_hIOCompletionPort,(ULONG_PTR)PerHandleData,0)==NULL){cout<<"客户端绑定失败"<<endl;closesocket(Clietnt);}else{_IO_DATA* data=new _IO_DATA;data = (_IO_DATA*)GlobalAlloc(GPTR, sizeof(_IO_DATA));memset(&data->m_Overlapped,0,sizeof(data->m_Overlapped));data->opCode=IO_READ;data->wsabuf.buf=data->buff;data->wsabuf.len=2048;DWORD nByte,dwFalgs=0;int iRet=WSARecv(Clietnt,&data->wsabuf,1,&nByte,&dwFalgs,&data->m_Overlapped,NULL);if(iRet==SOCKET_ERROR&&(GetLastError()!=ERROR_IO_PENDING)){cout<<"接受失败"<<endl;closesocket(Clietnt);delete data;continue;}}}closesocket(_socket);WSACleanup();return 0;}
因为细节方面忽略了很多,可能存在bug,所以先上main函数的代码了,下一期补充完全代码。



下一期:按照IOCP原理流程,一步一步实现代码!
点击上方蓝字关注我们
文章转载自小懵白生活小趣谈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




