完成端口WSARecv作用分析

ISABCJJ 2009-07-08 09:59:40
编写完成端口服务程序,主要就是以下几个步骤:

1、创建一个完成端口
2、根据CPU个数创建工作者线程,把完成端口传进去线程里
3、创建侦听SOCKET,把SOCKET和完成端口关联起来,并投递一个WSARecv操作
4、创建PerIOData,向连接进来的SOCKET投递WSARecv操作

5、线程里所做的事情:
a、GetQueuedCompletionStatus,在退出的时候就可以使用PostQueudCompletionStatus使线程退出
b、取得数据并处理

问题是小弟现在不是很明白第三步中在与完成端口关联后,投递一个WSARecv操作的作用是什么?或者说实际做了一个什么动作?
/*

完成端口服务器

接收到客户端的信息,直接显示出来

*/

#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);
}
...全文
2414 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
虚空骄阳 2012-05-18
  • 打赏
  • 举报
回复
比喻解释得很形象,终于弄明白了,谢谢大家
到处飘的云 2011-06-17
  • 打赏
  • 举报
回复
楼上的比喻很好
master_shifu 2009-07-09
  • 打赏
  • 举报
回复
WSARecv 只是向系统提交一个异步接收请求,这个请求会在有数据到达之后返回,并且放入完成队列通知工作线程,这个异步接收请求到此完成,继续提交请求是为了接收下一个数据包,也就是说,每次请求返回之后必须再次提交。

类似这样:
1. 你让一个前台去等待接待一个客人(WSARecv)然后继续做你的事情
2. 你的秘书会一直等着资料夹有新文件然后拿给你然后继续等待有新文件(loop)
3. 前台接待客人之后把客户资料放到资料夹之后就不会继续去前台接待客人了
4. 这个时候如果你不再派前台继续去等待接待客人(WSARecv),那么你的资料夹不会有新的资料了,这时就需要再次指派前台去等待接待新的客人(再次WSARecv)

就是这样
ISABCJJ 2009-07-08
  • 打赏
  • 举报
回复
对完成端口来说,将一个套结字邦定到完成端口后,WSARecv和WSASend会立即返回,提高了系统的效率。可以调用 GetQueuedCompletionStatus来判断WSARecv和WSASend是否完成。这样主程序就可以完全等待接受新的连接,线程程序来等待WSARecv和WSASend是否完成了。我想这也是完成端口的真正含义吧。
有了这种观点就可以理解参考2第8章的例子了。主线程接受到一个连接后,调用WSARecv等待该连接发送的数据(不阻塞,由完成端口实现数据的接受完毕判断)。在线程函数中接受完毕,然后用WSASend函数发送给客户数据(同样是不阻塞,直接返回,由完成端口判断数据是否发送完毕)。这样在线程函数中需要区分是发送完毕还是接受完毕。如果是发送完毕,需要调用一个WSARecv函数等待该连接下一次发送数据。

谢谢你!http://topic.csdn.net/t/20040512/08/3056877.html

哈哈 终于明白了
第2封信我知道在那里接收了
ISABCJJ 2009-07-08
  • 打赏
  • 举报
回复
就好比是,现在你远方有很多个朋友。
他们要给你发信的。这些个朋友就是客户端。
如果有个朋友打电话给你,告诉你要发信给你了。
这个时候就是accept。accept相当于你发吧,我会收的。

然后你就告诉邮局,有我的信到时候你告诉我。
这个过程就是wsarecv操作。

而邮局就是操作系统。

之后你就派你公司的闲人没事就去邮局问下,我的信来了没。这个就是GQCS,这些闲人就是工作者线程。
如果信来了,你就拿过来看下。

这个比如很好!
但是朋友和我电话联系后 发了2封信
第2封信如何收到
haggard_hunan 2009-07-08
  • 打赏
  • 举报
回复
编写完成端口服务程序,主要就是以下几个步骤:

1、创建一个完成端口
2、根据CPU个数创建工作者线程,把完成端口传进去线程里
3、创建侦听SOCKET,把SOCKET和完成端口关联起来,并投递一个WSARecv操作
4、创建PerIOData,向连接进来的SOCKET投递WSARecv操作

5、线程里所做的事情:
a、GetQueuedCompletionStatus,在退出的时候就可以使用PostQueudCompletionStatus使线程退出
b、取得数据并处理
//先不说这些,这个处理就是不规范的,大型程序中这样处理问题会一大堆,详细你可以找一个IOCP的开源看一下就明白道理了

问题是小弟现在不是很明白第三步中在与完成端口关联后,投递一个WSARecv操作的作用是什么?或者说实际做了一个什么动作?

//就好像一个装水的池子,当池子有水时,你永远不把它取出来,满了以后就再也放不进了,WSARecv就是取水
大前置 2009-07-08
  • 打赏
  • 举报
回复

当接收数据完成时,发一个信息给客户端,结束数据传送
这时,客户端会断开连接,服务器也断开连接。
这是正常退出


如果不发送结束标志,客户端也会知道服务器已断开,

但是,好一点的编程员,当出现异常断开时,会认为无效数据。。。
只有接收到服务器结束标志,才认为是正常的数据传输行为。。
s104892992 2009-07-08
  • 打赏
  • 举报
回复
关注
ISABCJJ 2009-07-08
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 ifeelhappy 的回复:]
你投递recv的意思是,让完成端口告诉系统,如果我这个客户端有包过来,接收完成了,告诉我。我在GetQCS里面等你告诉我结果。

你不投递,操作系统就认为客户只是连接,然后不做任何事情。

回答的不够完整。有问题再问。
[/Quote]

那客户端下一次发包是怎么接收的?其实我是想问题是在第三步中在与完成端口关联后,投递一个WSARecv中完成接收客户端下一个包还是在工作者线程中完成?
ifeelhappy 2009-07-08
  • 打赏
  • 举报
回复
你投递recv的意思是,让完成端口告诉系统,如果我这个客户端有包过来,接收完成了,告诉我。我在GetQCS里面等你告诉我结果。

你不投递,操作系统就认为客户只是连接,然后不做任何事情。

回答的不够完整。有问题再问。
Walf_ghoul 2009-07-08
  • 打赏
  • 举报
回复
路过,帮顶下。。。

64,683

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧