完成端口的详细说明可以参考下面这两篇文章,理论讲的很好。
手把手教你玩转SOCKET模型:完成端口(Completion Port)详解
完成端口(I/O completion)原理 收藏
但是本文的实现和它们还是有点区别。
这里就只列出完成端口服务器端的基本流程了。
首先,说说主线程:
1.创建完成端口对象
2.创建工作者线程(这里工作者线程的数量是按照CPU的个数来决定的,这样可以达到最佳性能)
3.创建监听套接字,绑定,监听,然后程序进入循环
(2). 将该客户端套接字与完成端口绑定到一起(还是调用CreateIoCompletionPort,但这次的作用不同),注意,按道理来讲,此时传递给 CreateIoCompletionPort的第三个参数应该是一个完成键,一般来讲,程序都是传递一个单句柄数据结构的地址,该单句柄数据包含了和该 客户端连接有关的信息,由于我们只关心套接字句柄,所以直接将套接字句柄作为完成键传递;
(3).触发一个WSARecv异步调用,这次又用到了“尾随数据”,使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后,此外,还有操作类型等重要信息。
在工作者线程的循环中:
1.调用GetQueuedCompletionStatus取得本次I/O的相关信息(例如套接字句柄、传送的字节数、单I/O数据结构的地址等等)
2.通过单I/O数据结构找到接收数据缓冲区,然后将数据原封不动的发送到客户端
3.再次触发一个WSARecv异步操作
下面是完成端口,socket连接,工作线程之间的关系。
服务端代码如下,好多代码和前面重叠IO都比较类似。客户端代码和前面是一样的。
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #include <iostream> #pragma comment(lib, "ws2_32.lib") using namespace std; #define PORT 6000 //#define IP_ADDRESS "10.11.163.113" //表示服务器端的地址 #define IP_ADDRESS "127.0.0.1" //直接使用本机地址 #define MSGSIZE 1024 //与重叠IO结构相关的一些信息,把它们封装在一个结构体中方便管理 class PerSocketData { public: WSAOVERLAPPED overlap;//每一个socket连接需要关联一个WSAOVERLAPPED对象 WSABUF buffer;//与WSAOVERLAPPED对象绑定的缓冲区 char szMessage[MSGSIZE];//初始化buffer的缓冲区 DWORD NumberOfBytesRecvd;//指定接收到的字符的数目 DWORD flags; }; //使用这个工作线程来通过重叠IO的方式与客户端通信 DWORD WINAPI workThread(LPVOID lpParam) { HANDLE completionPort=(HANDLE)lpParam; DWORD dwBytesTransfered; SOCKET clientSocket; PerSocketData * lpIOdata=NULL; while(true) { GetQueuedCompletionStatus( completionPort, &dwBytesTransfered, (LPDWORD)&clientSocket, (LPOVERLAPPED*)&lpIOdata, INFINITE); if (dwBytesTransfered==0xFFFFFFFF) { return 0; } if (dwBytesTransfered==0) { cout<<"客户端退出"<<endl; closesocket(clientSocket); HeapFree(GetProcessHeap(),0,lpIOdata); } else { cout<<lpIOdata->szMessage<<endl; send(clientSocket,lpIOdata->szMessage,dwBytesTransfered+1,0);//多发送一个字符,将字符串结束符也发送过去 memset(lpIOdata,0,sizeof(PerSocketData)); lpIOdata->buffer.len=MSGSIZE; lpIOdata->buffer.buf=lpIOdata->szMessage; WSARecv(clientSocket, &lpIOdata->buffer, 1, &lpIOdata->NumberOfBytesRecvd, &lpIOdata->flags, &lpIOdata->overlap, NULL); } } return 0; } void main() { WSADATA wsaData; int err; //1.加载套接字库 err=WSAStartup(MAKEWORD(1,1),&wsaData); if (err!=0) { cout<<"Init Windows Socket Failed::"<<GetLastError()<<endl; return ; } //下面执行一些使用完成端口需要进行的步骤 //创建一个完成端口 HANDLE completionPort=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0); PerSocketData * sockData; SYSTEM_INFO systeminfo; GetSystemInfo(&systeminfo); DWORD dwThreadId; for (int i=0;i<systeminfo.dwNumberOfProcessors;i++) { CreateThread(NULL,0,workThread,completionPort,0,&dwThreadId); } //2.创建socket //套接字描述符,SOCKET实际上是unsigned int SOCKET serverSocket; serverSocket=socket(AF_INET,SOCK_STREAM,0); if (serverSocket==INVALID_SOCKET) { cout<<"Create Socket Failed::"<<GetLastError()<<endl; return ; } //服务器端的地址和端口号 struct sockaddr_in serverAddr,clientAdd; serverAddr.sin_addr.s_addr=inet_addr(IP_ADDRESS); serverAddr.sin_family=AF_INET; serverAddr.sin_port=htons(PORT); //3.绑定Socket,将Socket与某个协议的某个地址绑定 err=bind(serverSocket,(struct sockaddr*)&serverAddr,sizeof(serverAddr)); if (err!=0) { cout<<"Bind Socket Failed::"<<GetLastError()<<endl; return ; } //4.监听,将套接字由默认的主动套接字转换成被动套接字 err=listen(serverSocket,10); if (err!=0) { cout<<"listen Socket Failed::"<<GetLastError()<<endl; return ; } cout<<"服务器端已启动......"<<endl; int addrLen=sizeof(clientAdd); SOCKET sockConn; while(true) { //5.接收请求,当收到请求后,会将客户端的信息存入clientAdd这个结构体中,并返回描述这个TCP连接的Socket sockConn=accept(serverSocket,(struct sockaddr*)&clientAdd,&addrLen); if (sockConn==INVALID_SOCKET) { cout<<"Accpet Failed::"<<GetLastError()<<endl; return ; } cout<<"客户端连接:"<<inet_ntoa(clientAdd.sin_addr)<<":"<<clientAdd.sin_port<<endl; //将之前的第6步替换成了下面的操作 CreateIoCompletionPort((HANDLE)sockConn,completionPort,(DWORD)sockConn,0); sockData=(PerSocketData*)HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PerSocketData)); sockData->buffer.len=MSGSIZE; sockData->buffer.buf=sockData->szMessage; WSARecv( sockConn, &sockData->buffer, 1, &sockData->NumberOfBytesRecvd, &sockData->flags, &sockData->overlap, NULL); } PostQueuedCompletionStatus(completionPort,0xFFFFFFFF,0,NULL); CloseHandle(completionPort); closesocket(serverSocket); //7.清理Windows Socket库 WSACleanup(); }
执行测试:
可执行文件可以在这里下载,工程文件可以在这里下载。
参考:
Windows Socket五种I/O模型