WIN下最好的网络模型可能就是IOCP完成端口了吧
经过几天的研究可以使用以下比喻来理解完成端口,
完成端口中的完成表示IO操作已经完成后才通知程序,完成端口可以更形象的想象成是完成队列
这里我将完成队列想象成一个管道。
网络库初始化部分省略
第一步:首先主负责人(主线程)在一个房子(服务器)里建立一个管道
程序实现
HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);
第二步:然后再安排N个工人(工作线程)去管道的末端让他们去等待管道了有数据流出,然后再处理这些数据(工作线程函数)
程序实现
SYSTEM_INFO si;
GetSystemInfo(&si);
DWORD dwThreadCnt = si.dwNumberOfProcessors * 2;
for(DWORD i = 0; i < dwThreadCnt; i++)
{
DWORD dwThreadId;
HANDLE hThread = CreateThread(NULL,0,WorkerThread,(LPVOID)hIOCP,0,&dwThreadId);
cout<<"创建工作线程["<<i<<"]"<<endl;
CloseHandle(hThread);
}
第三步 :然后给房子(服务器)开一扇窗户(端口),监视(监听),并设置一个队伍,让外来连接排队等候
程序实现
SOCKADDR_IN saServer;
saServer.sin_family = AF_INET;
saServer.sin_port = htons(PORT);
saServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
SOCKET sListen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);
bind(sListen,SOCKADDR*)&saServer,sizeof(saServer));
listen(sServer,SOMAXCONN);
第四步:按队列顺序来循环等待外来连接,当有连接时就接受连接,然后将新来连接的一些信息(j即自定义的PER_HEADER_DATA结构包括socker和SOCKADDR_IN)作为关键值与管道(完成端口)进行绑定,并且告诉他在管道末端有人在等待接受你的IO请求,同时给他一个箱子用来保存完成IO请求的信息(自定的PER_IO_DATA),在这里我使用的是WSARecv请求
一直等到关闭这个窗户,通知这些被安排的工人结束工作(停止工作线程)
程序实现
while(1)
{
SOCKADDR_IN saClient;
int sLen = sizeof(saClient);
SOCKET sClient = accept(sServer,(SOCKADDR*)&saClient,&sLen);
cout<<"接受到来自"<<inet_ntoa(saClient.sin_addr)<<" : "<<ntohl(saClient.sin_port)<<"的连接"<<endl;
LPPER_HANDLE_DATA pHandleData = new PER_HANDLE_DATA;
pHandleData->socket = sClient;
memcpy(&(pHandleData->sockaddr),&saClient,sizeof(saClient));
CreateIoCompletionPort((HANDLE)sClient,hIOCP,(ULONG_PTR)pHandleData,0);
LPPER_IO_DATA pIoData = new PER_IO_DATA;
ZeroMemory(pIoData,sizeof(PER_IO_DATA));
pIoData->wsaBuf.buf = pIoData->szBuf;
pIoData->wsaBuf.len = MAX_BUFF_SIZE;
DWORD dwSize;
DWORD dwFlags = 0;
WSARecv(sClient,&(pIoData->wsaBuf),1,&dwSize,&dwFlags,&(pIoData->overlapped),NULL);
}
DWORD dwByteTrans = 0;
PostQueuedCompletionStatus(hIOCP,dwByteTrans,0,0);
closesocket(sServer);
return 0;
工人们轮流等待管道中有数据流出(等待IOCP队列有完成操作),当有数据从管道中流出时,工人们通过流出的一个箱子和一个关键值来获取这次数据的信息
从关键值(PER_HANDLE_DATA)中就可以获取信息是来自哪一个连接,从信息箱子中可以获取到此次数据是哪一种IO操作,即它的一些数据信息,
处理完这些信息之后,又告诉这个连接说他们在管道末端已经开始准备你的下一次IO操作了
代码实现
DWORD WINAPI WorkerThread(LPVOID lpParameter)
{
HANDLE hIocp = (HANDLE)lpParameter;
while(1)
{
DWORD dwByte = 0;
PER_HANDLE_DATA *pHandleData = NULL;
PER_IO_DATA *pIoData = NULL;
if( 0 == GetQueuedCompletionStatus(hIocp,&dwByte,(ULONG_PTR*)&pHandleData,(LPOVERLAPPED*)&pIoData,INFINITE))
{
if((GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED))
{
cout<<"closing socket"<<inet_ntoa(pHandleData->sockaddr.sin_addr)<<endl;
closesocket(pHandleData->socket);
delete pHandleData;
delete pIoData;
continue;
}
else
{
cout<<"GetQueueCompletionStatus失败,错误代码:"<<GetLastError()<<endl;
return 0;
}
}
//说明客户端已经退出
if(dwByte == 0)
{
cout<<"closing socket"<<inet_ntoa(pHandleData->sockaddr.sin_addr)<<endl;
closesocket(pHandleData->socket);
delete pHandleData;
delete pIoData;
continue;
}
//处理相关数据
cout<<"接受来自"<<inet_ntoa(pHandleData->sockaddr.sin_addr)<<"的消息:"<<pIoData->wsaBuf.buf<<endl;
//继续投递下一次WSARecv操作
DWORD dwFlags = 0;
DWORD dwByteRecv = 0;
ZeroMemory(pIoData,sizeof(PER_IO_DATA));
pIoData->wsaBuf.buf = pIoData->szBuf;
pIoData->wsaBuf.len = MAX_BUFF_SIZE;
WSARecv(pHandleData->socket,&(pIoData->wsaBuf),1,&dwByteRecv,&dwFlags,&(pIoData->overlapped),NULL);
}
return 0;
}