从4秒钟发送5GB数据所引发的对IOCP重叠操作的思索

Squall_zy 2012-12-28 04:45:28
  先澄清一下,标题只是个噱头,所谓发送5GB数据实为重叠投递5GB的发送数据,并非真的发送了5GB数据。写这个帖子只是因为最近看了很多IOCP的文章,我觉得大部分的文章都会误导读者对IOCP的使用。这里我只想表述下我对IOCP的理解。同时希望抛砖引玉,得到更多的信息。
  4秒投递5GB,的确是可以做到的。这意味着你瞬间就安排了发送工作,而眼看着对面的机器花几十分钟的时间来完成对数据的接收。(注意,这几十分钟时间内,发送端应用层并没有做任何send动作)。
  在这种情况下,IOCP的发送可以达到最高的效率,唯有这种方式才是发挥了Overlapped的优势。在这种情况下,发送操作是使用用户Buf直接发的,而不需要通过socket的发送缓冲区。(这是性能的关键)

在这里,我想举个反例,这是我看到的文章中犯的最多的一个错误。看下面的结构体:
typedef struct _PER_IO_CONTEXT
{
OVERLAPPED m_Overlapped;
WSABUF m_wsaBuf;
char m_szBuffer[MAX_BUFFER_LEN]; (这里就是问题!!
OPERATION_TYPE m_OpType;
} PER_IO_CONTEXT;

外部调用接口:
BOOL PostSend( ..., ..., LOVOID pBuf, UINT nSize );

我仅从内存拷贝方面说明上例为何效率不高。
1、还是结合实际应用吧,这样最能说明问题。通常发送TCP数据,要么是定长报文,要么是带头报文。我暂且不说在带头报文长度大于MAX_BUFFER_LEN时,这东西都不能用。假设要发送带头报文,用户首先要开辟一块内存,把报头和身体填充好(这是一次内存拷贝);在调用PostSend函数时,IOCP类会把用户内存拷贝至m_szBuffer变量中作为投递缓存实体(这是第二次内存拷贝);当协议栈处于非SEND_PENDING状态下,WSASend函数会将m_szBuffer的内容拷贝至发送缓冲区(这是第三次内存拷贝);让人惊讶的是,为了发送一个TCP报文,居然消耗了三次内存拷贝的资源和时间。这就是效率低下的根源。
2、还有,你要投递5GB的数据,如果使用如上的结构体,你根本连内存都开不出来。这是因为上面的结构体人为地为每一次发送准备一块内存。假设你要把一个相同的文件同时发给10000个客户端,用上面这个例子会立刻让你服务器瘫痪。
3、真正享受到Overlapped性能的程序,只会使用一次内存拷贝,而不是三次!这需要用户来提供一块发送Buf,并且对该Buf的生存周期负责任;另外,想办法让系统尽量工作于SEND_PENDING状态,免去到发送缓冲区的内存拷贝。
4、最后一个问题是发送缓冲区、接收缓冲区是否该置0的讨论。这个讲起来理论有点多,我只能说各有各的好处。我个人使用不会置0,但如果你对服务器总处于重叠投递状态非常有把握,置0丝毫不会影响性能。但如果你没有把握,那么置0会延后客户端的发送时机(虽然少一次内存拷贝,但滞后了响应时间,所以得不偿失)。
...全文
644 26 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
WinEggDrop 2013-01-29
  • 打赏
  • 举报
回复
1.如果你的发送缓冲区不设置为0,那么你所有的WSA_Send()投递全要使用系统的非分页内存,每投递1M数据,就占用了1M非分页内存,直至数据传送成功(WSA_Send投递成功完成)。如果你投递了5GB数据,那么5GB非分页内存就全没了,先不说你的系统是否有5GB非分页内存(非分页内存只占物理内存的一部分,不同操作系统最高占用的百分比也不同,VISTA 64位也只能最多占用了40%的总物理内存),非分页内存被耗光的会怎样?轻则有的操作都返回WSAENOBUFS,重则系统网溃,因为所有网络操作及系统驱动都要使用非分页内存。 2.如果你发送缓冲区设置为0,那是根本没怎样用到非分页内存,但你的投递变成阻塞了,就是每个WSA_Send()投递都要数据发送到对方,及对方返回ACK后你的WSA_Send()才算是完成,那估计上面所说的投递5GB数据,发送方也得花和小时,不存在4秒完成。 如果是情况1,那么在现在大内存及64位操作系统下,要达到5GB非分页内存,并非不可能的(如果你有16GB物理内存或以上就没问题了),但是就算实现几秒投递了5GB数据出去,也根本意义不大,因为所有数据还是被锁在系统的内核发送区排成队列等着被发送出去(因为内核发送缓冲区也早就满了)
ithzhang 2013-01-21
  • 打赏
  • 举报
回复
重复利用前面投递好的缓冲区,不过对性能会有影响。不能发挥重叠IO的最大效率。
Squall_zy 2013-01-07
  • 打赏
  • 举报
回复
引用 23 楼 zzz_zou 的回复:
32位系统一下投递5GB。。。。。。。。。。。。。。。。
标题这么写,就是为了突出重叠投递。别说5GB了,10GB照样投。
zzz_zou 2013-01-07
  • 打赏
  • 举报
回复
引用 21 楼 ok1234567 的回复:
我觉得: 内存是内存,指令是指令 “将一块内存发送了1000....遍”只是忙坏了指令,不关内存什么事的 如果能在32位系统下,一下子投递5GB不同的数据,那一定是内存高手! 一般情形下,内存拷贝只是执行效率问题,内存占用则是资源枯竭问题,如果后者无大碍,前者不必太过在意(为了简化控制,只要不是瓶颈)
32位系统一下投递5GB。。。。。。。。。。。。。。。。
ok1234567 2013-01-06
  • 打赏
  • 举报
回复
我觉得: 内存是内存,指令是指令 “将一块内存发送了1000....遍”只是忙坏了指令,不关内存什么事的 如果能在32位系统下,一下子投递5GB不同的数据,那一定是内存高手! 一般情形下,内存拷贝只是执行效率问题,内存占用则是资源枯竭问题,如果后者无大碍,前者不必太过在意(为了简化控制,只要不是瓶颈)
wjb_yd 2013-01-06
  • 打赏
  • 举报
回复
虽不知但觉厉
Squall_zy 2013-01-04
  • 打赏
  • 举报
回复


这是投递完成后的截图。5GB数据。
zzz_zou 2013-01-04
  • 打赏
  • 举报
回复
引用 12 楼 Squall_zy 的回复:
引用 11 楼 zzz_zou 的回复:4秒可以投递5GB的数据吗? 我表示很夸张啊,冒昧问下,投递完后,程序的内存多大? 非页面缓冲池大小多少? 我的测试程序里是将一块内存发送了1000....遍,所以程序本身没有内存占用的问题。IOCP为用户完成发送,但本身使用用户所传递的地址,这也是我主要想讲的一个问题。这个问题我在结论2里已经说明了。 ……
我很好奇,你存不存在问题不是我关心的,你可以把你测试的结果给我说下吗,谢谢啦。
Eleven 2013-01-04
  • 打赏
  • 举报
回复
九州剑王 2013-01-04
  • 打赏
  • 举报
回复
看看各路高人怎么说的
Squall_zy 2013-01-04
  • 打赏
  • 举报
回复
引用 11 楼 zzz_zou 的回复:
4秒可以投递5GB的数据吗? 我表示很夸张啊,冒昧问下,投递完后,程序的内存多大? 非页面缓冲池大小多少?
我的测试程序里是将一块内存发送了1000....遍,所以程序本身没有内存占用的问题。IOCP为用户完成发送,但本身使用用户所传递的地址,这也是我主要想讲的一个问题。这个问题我在结论2里已经说明了。
xhz8000 2013-01-04
  • 打赏
  • 举报
回复
此贴必火!此贴必火!
zzz_zou 2013-01-04
  • 打赏
  • 举报
回复
引用 18 楼 wwwllg 的回复:
你看看虚拟内存大小,如果你真投递出去了,肯定有一个地方保存。 NT,及2003系统,会定时回收内存。 其实我都怀疑,你所谓的投递5GB数据,是如何操作的,贴上你的代码。
如果投递send,当socket缓冲区没有足够空间的时候,会锁定缓冲区,然后返回iopending,当socket有足够空间的时候,应用层的数据被直接丢给tcp协议栈,而不需要拷贝到socket缓冲区。这样看来,他是同一块内存连续投递10000次,那么内存或者虚拟内存应该不会有多少吧
UDX协议 2013-01-04
  • 打赏
  • 举报
回复
你看看虚拟内存大小,如果你真投递出去了,肯定有一个地方保存。 NT,及2003系统,会定时回收内存。 其实我都怀疑,你所谓的投递5GB数据,是如何操作的,贴上你的代码。
这个娜戒海了 2012-12-31
  • 打赏
  • 举报
回复
接分,20分拿来
zzz_zou 2012-12-31
  • 打赏
  • 举报
回复
4秒可以投递5GB的数据吗? 我表示很夸张啊,冒昧问下,投递完后,程序的内存多大? 非页面缓冲池大小多少?
昨夜无风 2012-12-31
  • 打赏
  • 举报
回复
对于大块内存,内存操作机制的确是个影响效率的问题! 但是我不敢苟同楼主说“投递5GB的数据,会给每一次发送都要准备一次内存”,我的想法是既然你有5G的数据,必定有他存在的地方(不管是内存还是硬盘上),我所需要做的就是取得数据的开始地址,然后移动指针去读就可以了。当然要高效率,还得进行内存管理!
UDX协议 2012-12-31
  • 打赏
  • 举报
回复
我的理解,楼主对IOCP,或对内存操作,略懂皮毛。
UDX协议 2012-12-31
  • 打赏
  • 举报
回复
不知道所云,云。。。
Squall_zy 2012-12-30
  • 打赏
  • 举报
回复
写这个帖子,只是因为看了网上太多的代码,觉得很多人被误导了,用a机制的思想去做b机制的事情。有谁敢说iocp和内存拷贝、缓冲区利用没有关系?这不是扯淡么。。。做服务器程序玩的就是内存,你看那些商业封装,人家那么多代码都在干什么?不就是在处理内存,解决效率问题么? 如果你觉得我的技术观点有错误,请指出!这个平台就是让大家去沟通讨论的。 如果不能拿出任何的观点,仅仅是上来喷一下爽快,那是不够负责任的。
加载更多回复(4)
最近有项目要做一个高性能网络服务器,决定下功夫搞定完成端口(IOCP),最终花了一个星期终于把它弄清楚了,并用C++写了一个版本,效率很不错。 但,从项目的总体需求来考虑,最终决定上.net平台,因此又花了一天一夜弄出了一个C#版,在这与大家分享。 一些心得体会: 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。
完成端口通讯服务器(IOCP Socket Server)设计 (六)功能强大的IOCP Socket Servre模块例程源码 Copyright © 2009 代码客(卢益贵)版权所有 QQ:48092788 源码博客:http://blog.csdn.net/guestcode 一、声明 版权声明: 1、通讯模块代码版权归作者所有; 2、未经许可不得全部或部分用于任何项目开发; 3、未经许可不得部分修改后再利用源码。 免责声明: 1、 由于设计缺陷或其它Bug造成的后果,作者不承担责任; 2、未经许可的使用作者不提供任何技术支持服务。 权利和义务: 1、任何获得源码并发现Bug的个人或单位均有义务向作者反映; 2、作者保留追究侵权者法律责任的权利。 二、开发背景 部分代码由前项目分离而来,尚未有应用考验,但对于初学者学习和进阶有很大帮助。性能上尚未有定论,但应该不会令你失望。 三、功能说明 1、可以关闭Socket的Buffer; 2、可以关闭MTU(不等待MTU满才发送); 3、可以多IP或多端口监听; 4、可以重用socket(主动关闭除外); 5、可以0缓冲接收(Socket的Buffe = 0时,避免过多的锁定内存页); 6、可以0缓冲连接(客户端仅连接,不一定立即发数据); 7、可以条件编译: a、是否使用内核Singly-linked lists; b、是否使用处理线程(工作线程和处理线程分开); c、是否使用内核锁来同步链表。 8、可以实现集群服务器模式的通讯(有客户端socket); 9、可以单独设置每个连接的Data项来实现连接和Usernfo的关联; 10、每个线程有OnBegin和OnEnd,用于设置线程独立的对象(数据库会话对象); 11、可以提供详细的运行情况,便于了解IOCP下的机制,以及进行调试分析; 12、可以发起巨量连接和数据(需要硬件配置来支持)。

18,363

社区成员

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

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