完成端口 返回PerIoData 的疑问

gotooker 2009-07-16 11:14:49
最近看了完成端口,很迷惑,GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)),其中PerHandleData是在 CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);种关联的,但是PerIoData却没有关联,我看到唯一可能有点线索的大概就是投递WSARecv 参数里面 PerIOData里的WSAOVERLAPPED了,难道完成端口是通过WSAOVERLAPPED找到PerIOData的指针,再返回,这也太智能了,感觉上也很难做到。谢谢解答

我是参考这个网址上的程序。http://hi.baidu.com/winnyang/blog/item/d9948a33acf21afe1b4cffd6.html

/*

完成端口服务器

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

*/

#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)
{
OutErr("WSAStartup()");
}
}

///////////////////////////////////////////////////////////////////////
//
// 函数名 : 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)
{
CloseHandle(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);
}

...全文
57 25 打赏 收藏 转发到动态 举报
写回复
用AI写文章
25 条回复
切换为时间正序
请发表友善的回复…
发表回复
ssdutcraig 2009-08-24
  • 打赏
  • 举报
回复
udknight没看明白,PostQueuedCompletionStatus是退出的意思。
udknight 2009-07-17
  • 打赏
  • 举报
回复
手头上有个10万客户端的服务器,一直运行很正常。你应该测试下new 1万个 SOCKET 和直接从 SOCKET池取 速度和效率的差别。如果你试过就不会这样说了
udknight 2009-07-17
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 fangle6688 的回复:]
引用 18 楼 udknight 的回复:
引用 15 楼 fangle6688 的回复:
我觉得不管长连接短连接,只要用户量一大,服务器每次都去new开销都会很大。服务器启动的时候做一个sokcet 池很有必要。至于你说的长连接的问题,我觉得并不影响套接字回收。不管你是长是短,服务器运行稳定后断开的连接数量就很客观了…




凡事不要想当然
你自己用最WSAAccept写个监听程序,再写个客户端开多线程对服务端保持每秒10个以上的连接请求,…
[/Quote]
无意义
udknight 2009-07-17
  • 打赏
  • 举报
回复
算过效率没?
udknight 2009-07-17
  • 打赏
  • 举报
回复
算了,不懂优化的人没必要说下去
fangle6688 2009-07-17
  • 打赏
  • 举报
回复
[Quote=引用 18 楼 udknight 的回复:]
引用 15 楼 fangle6688 的回复:
我觉得不管长连接短连接,只要用户量一大,服务器每次都去new开销都会很大。服务器启动的时候做一个sokcet 池很有必要。至于你说的长连接的问题,我觉得并不影响套接字回收。不管你是长是短,服务器运行稳定后断开的连接数量就很客观了…
[/Quote]


凡事不要想当然
你自己用最WSAAccept写个监听程序,再写个客户端开多线程对服务端保持每秒10个以上的连接请求,连续跑几天
最后统计下连接请求总次数和成功次数,再看看你服务端的内存消耗有多大变化

——我做过这样的测试,你有没有???
udknight 2009-07-17
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 fangle6688 的回复:]
既然是为了处理高“并发”连接,套接字“重用”还有何意义?
“连接数直线上升”,说明用户不断连入,而用户退出却很少,又哪来的套接字可以“重用”?
[/Quote]
我觉得不管长连接短连接,只要用户量一大,服务器每次都去new开销都会很大。服务器启动的时候做一个sokcet 池很有必要。至于你说的长连接的问题,我觉得并不影响套接字回收。不管你是长是短,服务器运行稳定后断开的连接数量就很客观了。服务器刚起开阶段连接数本来就会直线上升,运行一段时间连接数将会平稳。至于你用不用AcceptEx那是你的事情了。严重不推荐不代表别人都不用。
  • 打赏
  • 举报
回复
学习一下。支持7楼的回答!
gotooker 2009-07-17
  • 打赏
  • 举报
回复
感谢楼上两位深夜回帖,”只有AcceptEx可以实现套接字重用 “ ??设置ReUSeADDr不可以吗??我看看书先
fangle6688 2009-07-17
  • 打赏
  • 举报
回复
[Quote=引用 21 楼 udknight 的回复:]
手头上有个10万客户端的服务器,一直运行很正常。你应该测试下new 1万个 SOCKET 和直接从 SOCKET池取 速度和效率的差别。如果你试过就不会这样说了
[/Quote]


哈哈哈哈,走远点吧

偶认真测试过,不是1万个,是100万个,100万次操作速度差距不到10秒
对于长连接应用而言,使用SOCKET池带来的效率提升非常有限
相对使用AccpetEx带来的逻辑复杂度而言,可以忽略

大概微软对WSAccept的实现,或者对SOCKET资源管理本来就内含了一定的优化措施
所以,凡事都要试过再下结论!
fangle6688 2009-07-17
  • 打赏
  • 举报
回复
既然是为了处理高“并发”连接,套接字“重用”还有何意义?
“连接数直线上升”,说明用户不断连入,而用户退出却很少,又哪来的套接字可以“重用”?

fangle6688 2009-07-17
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 udknight 的回复:]
1 只有AcceptEx可以实现套接字重用,当服务器连接数直线上升的时候,套接字开销是很大的。
2 IOCP本来就是为了完成高并发的连接。它的目的就是为了同时处理成百上千个套接字。1 只有AcceptEx可以实现…
[/Quote]

一切都要看需求

现实的典型需求只有两种:

1、无用户状态,短连接应用,例如域名服务器
2、有用户状态,长连接应用,例如游戏服务器

前者需要频繁创建套接字,但IO负担并不大,所以,它适合AcceptEx,但没必要用IOCP
后者对IO负担很大,但无需频繁创建套接字,所以,它应该使用IOCP,但不必使用AcceptEx

所以,偶“严重不推荐在IOCP服务器中使用AcceptEx”
udknight 2009-07-16
  • 打赏
  • 举报
回复
这个写法比较怪异,仅仅是把PerIOData当缓冲区使用,投递WSARecv接受数据后,再PostQueuedCompletionStatus进行通知。这样写法完全丢失了完成端口的优势。
mmxpp 2009-07-16
  • 打赏
  • 举报
回复

mark
udknight 2009-07-16
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 fangle6688 的回复:]
严重不推荐在IOCP服务器中使用AcceptEx
ListenSocket放到单独线程中循环WSAAccept即可,不要关联完成端口,完成端口只处理收发包,不处理监听


IOCP不是为了让你能够响应大量的connect请求——这一点很容易做到,根本不需要使用IOCP机制

IOCP的本质是用适量的线程数来处理大量的IO请求,避免因使用大量线程而频繁切换线程上下文数据所导致的系统资源浪费
[/Quote]
1 只有AcceptEx可以实现套接字重用,当服务器连接数直线上升的时候,套接字开销是很大的。
2 IOCP本来就是为了完成高并发的连接。它的目的就是为了同时处理成百上千个套接字。
XD王 2009-07-16
  • 打赏
  • 举报
回复
Mark
正好也在看这个 《windows网络编程技术》第八章中也有介绍
codelast.com 2009-07-16
  • 打赏
  • 举报
回复
mark一下
fangle6688 2009-07-16
  • 打赏
  • 举报
回复
严重不推荐在IOCP服务器中使用AcceptEx
ListenSocket放到单独线程中循环WSAAccept即可,不要关联完成端口,完成端口只处理收发包,不处理监听


IOCP不是为了让你能够响应大量的connect请求——这一点很容易做到,根本不需要使用IOCP机制

IOCP的本质是用适量的线程数来处理大量的IO请求,避免因使用大量线程而频繁切换线程上下文数据所导致的系统资源浪费
udknight 2009-07-16
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 gotooker 的回复:]
恩,楼上我也是这么想的。

今天我测试了一下完成端口,不断的链接 然后关闭

我设置了ReuseAddr 并且关闭了LINGER

发现大概连接数到了4000左右的时候会停住 ,不在接受连接,但等待若干秒后又可以继续接受连接 然后又等待又可以接受连接。这正常吗??
[/Quote]
AcceptEx投递少了
fangle6688 2009-07-16
  • 打赏
  • 举报
回复
不正常。。。

我的测试结果是受理20000+长连接后响应速度有所下降
短连接跑几天都不会有啥反应。。。
加载更多回复(5)

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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