开门见山,直接就事论事。
假如有这么一个基于IOCP模型的Server,这个Server提供的所有服务中有这么一种服务……文件下载,我们再假设Server端存有一个20G的文件,客户端这时发送一个请求到服务端来,客户端要求下载这个20G的文件,由此可能引发一系列让人头疼的问题(不谈TransmitFile,我们谈WSASend)
先给出一段伪代码,这段代码肯定是有问题的,如下:
[html] view plaincopy
- //在某个线程里,我们开始向某个客户端疯狂发送这20G的文件了
- while(true)
- {
- char *data=new char[PER_SEND_DATA_LEN];
- ::ReadFile(...,data,PER_SEND_DATA_LEN,&len,...);
- io_data.wsabuf.buf=data;
- io_data.wsabuf.len=len;
- ::WSASend(...,&io_data.wsabuf,...);
- delete []data;
- if(len<PER_SEND_DATA_LEN) break;
- }
这样无节操的发送基本上会得到一个WSAENOBUFS(缓冲区不足)错误,这个不是什么大问题,不会影响到IOCP底层模块的设计,值得注意的是下面一个问题——数据发送不完全
接着上面的例子继续思考下去。
假如其中一个WSASend投递长度为PER_SEND_DATA_LEN的数据,而GetQueuedCompletionStatus(...,&BytesTransferred,...)返回结果却表明这次发送没有进行完全,BytesTransferred<PER_SEND_DATA_LEN,而后续的WSASend函数正源源不断地投递新的请求,即使我们在Get函数返回后,再次投递WSASend请求补发剩余数据,也无法保证这样的补发可以赶在其他WSASend的前面。
如果以上推测成立的话,那么数据将不可避免的出现乱序。
当然,这只是一种担心,实际上到目前为止,本人还没有遇到过这种情况,个人认为:这种情况应该不会发生,一定是哪些地方还没有搞透彻,否则重叠的意义何在?不过,网上担心这种情况的人也不少,以下是提出这些问题的链接(不保证都是原创):
http://blog.csdn.net/skiing_886/article/details/8044186
http://bbs.eyuyan.com/read.php?tid=306324
上面两个链接中,其中一个是篇博客,博客中明言WSASend带重叠结构投递请求时,不会出现数据发送不完全的情况,本人比较容易相信别人,于是我就信了。就算那篇博客说错了,本人也还是觉得数据乱序的情况不会发生。
下面,本人想就这个问题,结合IOCP的完成队列说一些自己的猜想(完全是猜想,假设WSASend所投递的数据有可能发送不完全)。
首先,IOCP维护着一个的队列,这个队列没有好妖魔化的,就是一个先进先出的数据结构,效率很高就是了。问题是,是不是我们每次调用WSASend都将导致一次入列,而每次GetQueuedCompletionStatus函数返回都会导致一次出列呢?
结合上面的那个下载文件的例子,细细一想,如果每次GetQueuedCompletionStatus返回都要出列一次数据包的话,那一旦出现数据发送不完全的情况,即使我们再次调用WSASend补发,那也只能将数据附加到完成队列的头部,乱序是必然的。
但是,换个思维,这种问题连我都能想到,微软那么多高智商人才难道都是吃干饭的吗?
所以,我认为,当数据发送不完全的时候,GetQueuedCompletionStatus应该是不会出列数据的,而可能是占着茅坑不拉屎,等待WSASend再次补发未完成的数据发送,而这个WSASend肯定不会导致数据数据重新入列,而只是给一个继续发送的信号。
如果是我的,我肯定会这样设计,否则,那就只能采取保险的做法,WSASend不重叠投递,一次投递之后需要等待Get函数返回再决定是要投递一个补发的WSASend请求还是投递下一个完整数据包的WSASend请求。这种做法虽然保险,但这突出了IOCP的巨大缺陷,正如我前面所说的,重叠的意义何在?WSASend还有必要带着个OVERLLAPED结构吗?
所以,我认为,这种情况的数据乱序不可能发生,不用担心,嗯,不担心……
说到最后,再顺便提一下STL。STL的出列操作是Pop,它是不带任何返回值的出列,要想知道出列了什么数据必须在Pop之前调用front方法。有人强烈鄙视这种设计,认为这种设计极其别扭。要知道,STL的设计者必然是高手,设计成这样自然有理由,队列这种东西,要想通用的话,最好还是要把‘看’和‘取’分开,STL的设计是综合考虑的结果。