PostQueuedCompletionStatus(
HANDLE CompletionPort,
DWORD dwNumberOfBytesTransferred,
ULONG_PTR dwCompletionKey,
LPOVERLAPPED lpOverlapped
)
关于这个函数,网上几乎都是......经常用于退出时发送一个模拟的IO完成事件来唤醒在等待中的线程,显然很多人都仅仅用在系统退出的时候作为唤醒线程作用。
然而在一些论文里面,我们才能看到对于这个函数的一些非常有用的论点。只有提及,没有具体的代码。但是这不重要。
因此,我个人判断,应该大多数人采用WSASend发送数据的时候,是通过主线程调用的。在这里我说下,这样可能不是很好。因为很明显,主线程直接参与到工作线程的调度当中了。不仅仅是效率问题了。具体会涉及到哪种问题,这个就不得而知了。这里不做讨论。
正确的做法是:我们应该调用PostQueuedCompletionStatus函数来发送数据:-》由IOCP在工作线程里面调用WSASend发送数据,把控制权交给工作者线程来调度。这样一来就完全变成所有的接收和发送都是由工作者线程来完成。主线程只需要处理完成后的数据就OK了。
因为我们知道,IOCP完成端口涉及到系统内核的各种问题,为了稳定和系统性能效率而言,采用主线程(外部处理程序)和工作者线程运行分离机制是比较明智的。
具体如何运用,看它的几个参数就OK,太简单了。
然而事情其实没有那么简单:
在调试中,我们发现多个线程在切换的时候,可以说是随机变化的。我们知道WSASend发送数据是可以重叠操作的,即不用等到完成返回,可以不断调用发送操作。这样一来,问题就来了,下一次重叠发送操作的时候,处理的线程必然是另外一条线程,但是系统处理的顺序不一定有序的,这就无法保证数据包是按照我们预想的那样发送出去。乱序就可能会发生了。这种情况或者在UDP模式下更明显。
但是我仍然强调使用PostQueuedCompletionStatus函数来发送数据,而避免直接由主线程来调用WSASend发送数据。在多线程编程的时候,我相信很多人都会遇上各种各样莫名其妙的问题。所以我的建议是尽量避免直接掺合到多线程运行里面去。
为了发挥IOCP重叠完成的高效性能,不需要每一次都等待完成返回后,再次调用WSASend发送,一般而言对同一个连接连续调用多次WSASend是安全的,要不然重叠的意义就不存在了。但是我们需要顾及到系统分页内存锁定极限和非分页内存的极限,显然不能允许同一个链接无限制地执行重叠操作,天知道会发生什么问题,这其实是绝对需要限制的。
要解决以上那些问题具体做法是:每一个链接只允许调用一次IO Send发送,我们通过一个发送数据缓冲区队列机制,假如有更多的后续发送数据,就填充到发送队列里面,或者采用一条TList列表来指向每一块缓冲区,当列表数量不等于零,那么返回后WSASend,就继续从列表里面取出一块数据,继续执行发送。或者维护一条定量的缓冲区模块。
显然,这样一来就解决了乱序的问题,以及不会降低重叠操作的效率问题。并且这种方法更稳定。
无论在什么场合下,数据的发送肯定是一条条的,不管你里面有多少个数据包,每一次发送必然是一个包,客户端接收数据不可能一次接收两个包吧。所以这里的重叠操作,仅仅指的是系统运行效率性能的问题。你再快,都是一个个包发过去,客户端也是一个个包地接收。
那么UDP完成端口也采用上面的解决方案合不合适,我个人感觉没有问题,毕竟UDP发送数据,可不管你接收到与否,它发送完毕是立即返回的,速度是非常快的,因为到最后,你都需要限制重叠次数,这个不用多说。虽然没有链接,实际上,系统是需要维护每一个链接进来的客户端对象的