完成端口WSARecv作用分析
编写完成端口服务程序,主要就是以下几个步骤:
1、创建一个完成端口
2、根据CPU个数创建工作者线程,把完成端口传进去线程里
3、创建侦听SOCKET,把SOCKET和完成端口关联起来,并投递一个WSARecv操作
4、创建PerIOData,向连接进来的SOCKET投递WSARecv操作
5、线程里所做的事情:
a、GetQueuedCompletionStatus,在退出的时候就可以使用PostQueudCompletionStatus使线程退出
b、取得数据并处理
问题是小弟现在不是很明白第三步中在与完成端口关联后,投递一个WSARecv操作的作用是什么?或者说实际做了一个什么动作?
- C/C++ code
/* 完成端口服务器 接收到客户端的信息,直接显示出来*/#include "winerror.h"#include "Winsock2.h"#pragma comment(lib, "ws2_32")#include "windows.h"#include <iostream>using namespace std;/// 宏定义#define PORT 5050#define DATA_BUFSIZE 8192#define OutErr(a) cout << (a) << endl << "出错代码:" << WSAGetLastError() << endl << "出错文件:" << __FILE__ << endl << "出错行数:" << __LINE__ << endl#define OutMsg(a) cout << (a) << endl;/// 全局函数定义/////////////////////////////////////////////////////////////////////////// 函数名 : InitWinsock// 功能描述 : 初始化WINSOCK// 返回值 : void/////////////////////////////////////////////////////////////////////////void InitWinsock(){ // 初始化WINSOCK WSADATA wsd; if( WSAStartup(MAKEWORD(2, 2), &wsd) != 0) }/////////////////////////////////////////////////////////////////////////// 函数名 : BindServerOverlapped// 功能描述 : 绑定端口,并返回一个 Overlapped 的Listen Socket// 参数 : int nPort// 返回值 : SOCKET/////////////////////////////////////////////////////////////////////////SOCKET BindServerOverlapped(int nPort){ // 创建socket SOCKET sServer = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); // 绑定端口 struct sockaddr_in servAddr; servAddr.sin_family = AF_INET; servAddr.sin_port = htons(nPort); servAddr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sServer, (struct sockaddr *)&servAddr, sizeof(servAddr)) < 0) { OutErr("bind Failed!"); return NULL; } // 设置监听队列为200 if(listen(sServer, 200) != 0) { OutErr("listen Failed!"); return NULL; } return sServer;}/// 结构体定义typedef struct{ OVERLAPPED Overlapped; WSABUF DataBuf; CHAR Buffer[DATA_BUFSIZE];} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;typedef struct{ SOCKET Socket;} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;DWORD WINAPI ProcessIO(LPVOID lpParam){ HANDLE CompletionPort = (HANDLE)lpParam; DWORD BytesTransferred; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_OPERATION_DATA PerIoData; while(true) { if(0 == GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)) { if( (GetLastError() == WAIT_TIMEOUT) || (GetLastError() == ERROR_NETNAME_DELETED) ) { cout << "closing socket" << PerHandleData->Socket << endl; closesocket(PerHandleData->Socket); delete PerIoData; delete PerHandleData; continue; } else { OutErr("GetQueuedCompletionStatus failed!"); } return 0; } // 说明客户端已经退出 if(BytesTransferred == 0) { cout << "closing socket" << PerHandleData->Socket << endl; closesocket(PerHandleData->Socket); delete PerIoData; delete PerHandleData; continue; } // 取得数据并处理 cout << PerHandleData->Socket << "发送过来的消息:" << PerIoData->Buffer << endl; // 继续向 socket 投递WSARecv操作 DWORD Flags = 0; DWORD dwRecv = 0; ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA)); PerIoData->DataBuf.buf = PerIoData->Buffer; PerIoData->DataBuf.len = DATA_BUFSIZE; WSARecv(PerHandleData->Socket, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL); } return 0;}void main(){ InitWinsock(); HANDLE CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); // 根据系统的CPU来创建工作者线程 SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); for(int i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++) { HANDLE hProcessIO = CreateThread(NULL, 0, ProcessIO, CompletionPort, 0, NULL); if(hProcessIO) } // 创建侦听SOCKET SOCKET sListen = BindServerOverlapped(PORT); SOCKET sClient; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_OPERATION_DATA PerIoData; while(true) { // 等待客户端接入 //sClient = WSAAccept(sListen, NULL, NULL, NULL, 0); sClient = accept(sListen, 0, 0); cout << "Socket " << sClient << "连接进来" << endl; PerHandleData = new PER_HANDLE_DATA(); PerHandleData->Socket = sClient; // 将接入的客户端和完成端口联系起来 CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0); // 建立一个Overlapped,并使用这个Overlapped结构对socket投递操作 PerIoData = new PER_IO_OPERATION_DATA(); ZeroMemory(PerIoData, sizeof(PER_IO_OPERATION_DATA)); PerIoData->DataBuf.buf = PerIoData->Buffer; PerIoData->DataBuf.len = DATA_BUFSIZE; // 投递一个WSARecv操作 DWORD Flags = 0; DWORD dwRecv = 0; WSARecv(sClient, &PerIoData->DataBuf, 1, &dwRecv, &Flags, &PerIoData->Overlapped, NULL); } DWORD dwByteTrans; PostQueuedCompletionStatus(CompletionPort, dwByteTrans, 0, 0); closesocket(sListen);}
[解决办法]
你投递recv的意思是,让完成端口告诉系统,如果我这个客户端有包过来,接收完成了,告诉我。我在GetQCS里面等你告诉我结果。
你不投递,操作系统就认为客户只是连接,然后不做任何事情。
回答的不够完整。有问题再问。
[解决办法]
当接收数据完成时,发一个信息给客户端,结束数据传送
这时,客户端会断开连接,服务器也断开连接。
这是正常退出
如果不发送结束标志,客户端也会知道服务器已断开,
但是,好一点的编程员,当出现异常断开时,会认为无效数据。。。
只有接收到服务器结束标志,才认为是正常的数据传输行为。。
[解决办法]
编写完成端口服务程序,主要就是以下几个步骤:
1、创建一个完成端口
2、根据CPU个数创建工作者线程,把完成端口传进去线程里
3、创建侦听SOCKET,把SOCKET和完成端口关联起来,并投递一个WSARecv操作
4、创建PerIOData,向连接进来的SOCKET投递WSARecv操作
5、线程里所做的事情:
a、GetQueuedCompletionStatus,在退出的时候就可以使用PostQueudCompletionStatus使线程退出
b、取得数据并处理
//先不说这些,这个处理就是不规范的,大型程序中这样处理问题会一大堆,详细你可以找一个IOCP的开源看一下就明白道理了
问题是小弟现在不是很明白第三步中在与完成端口关联后,投递一个WSARecv操作的作用是什么?或者说实际做了一个什么动作?
//就好像一个装水的池子,当池子有水时,你永远不把它取出来,满了以后就再也放不进了,WSARecv就是取水
[解决办法]
WSARecv 只是向系统提交一个异步接收请求,这个请求会在有数据到达之后返回,并且放入完成队列通知工作线程,这个异步接收请求到此完成,继续提交请求是为了接收下一个数据包,也就是说,每次请求返回之后必须再次提交。
类似这样:
1. 你让一个前台去等待接待一个客人(WSARecv)然后继续做你的事情
2. 你的秘书会一直等着资料夹有新文件然后拿给你然后继续等待有新文件(loop)
3. 前台接待客人之后把客户资料放到资料夹之后就不会继续去前台接待客人了
4. 这个时候如果你不再派前台继续去等待接待客人(WSARecv),那么你的资料夹不会有新的资料了,这时就需要再次指派前台去等待接待新的客人(再次WSARecv)
就是这样