从4秒钟发送5GB数据所引发的对IOCP重叠操作的思索
先澄清一下,标题只是个噱头,所谓发送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会延后客户端的发送时机(虽然少一次内存拷贝,但滞后了响应时间,所以得不偿失)。