1 概述
IO完成端口是windows下性能最好的IO模型。
windows下的socket也是一种IO,所以完成端口也是最好的socket IO模型。
不幸的是,IO完成端口非常复杂,理解它门槛已经非常很高,而正确使用的门槛比理解它还要高很多。
之所以这么难理解,很大原因也在于没有好的资料。MSDN的文档已经相当良心的完善,但是还是有许多的问题没有解释清楚,整个模型的机制模糊不清。而网上的文档只能作为入门,里面的内容离实际可用的代码有十万八千里那么远,比如很多的文档连发送的功能都没有讲到。至于udp使用iocp,就更少有资料了。
2 简化模型图
这个最简单的模型,用来表示IOCP涉及到的对象。
这里有三个对象,主线程,socket工作线程,系统API模块。
上面是用户代码层,下面是系统的IOCP API层。主线程和工作线程都处于用户代码层。
3 简化流程图
这个最简单的流程图,表示了主线程和Socket工作线程的主要流程。
简单的描叙一下:
(1)主线程通常是消息循环线程。主线程调用系统IOCP API发起操作(如WSARecv开始收Socket数据),请求系统完成该操作。
(2)系统收到操作请求,进行处理(如开始收Socket数据),并在处理完成时(如收到了60K数据),发出事件,事件会唤醒工作线程。
(3)工作线程通常是普通线程。阻塞等待事件,处理事件,并发送消息通知主线程(如"收到60K的Socket数据消息")。
(4)主线程收到消息,处理消息(如"收到60K的Socket数据消息"),处理业务逻辑(比如在界面上显示:你收到一条新的消息)。
4 模型要素
4.1 异步操作
异步操作是一个函数,该函数立即返回,作用是请求系统完成一些事情,系统经过一段时间完成了,就触发一个系统事件,唤醒等待事件的线程。
异步操作有:WSASend(发送数据),WSARecv(接受数据),PostQueuedCompletionStatus(发自定义事件)。
4.2 系统事件
系统事件有:Socket事件(发送SOCKET数据完成事件,收到数据事件,SOCKET断开连接事件),自定义事件:通过PostQueuedCompletionStatus发送的事件。
4.3 主线程
主线程通常是业务逻辑的所在,我们这里假设主线程是消息循环线程。
主线程建立和销毁工作线程,建立的过程很简单,这里略去,销毁的时候,是通过发送一个事件给工作线程,工作线程被唤醒并处理事件,退出。
主线程可以调用系统IOCP函数(如WSARecv函数接受Socket数据),该函数立即返回,不阻塞主线程。
主线程会收到工作线程发过来的消息(例如收到数据消息)。
4.4 工作线程
工作线程通常不做复杂的业务逻辑,是一个普通线程。
工作线程阻塞等待系统事件(GetQueuedCompletionStatus),每当收到系统事件,就会被唤醒,然后简单处理事件:例如把收到的Socket数据包装成一个消息,发送给主线程。
5 事件和操作
5.1 事件和操作之间基本关系
(1)只有发出操作,才可能收到事件。不发出操作,不会收到事件。
(2)发出一个操作,收到一个事件。
5.2 WSARecv操作
有可能收到“SOCKET收到数据事件”,“SOCKET断开连接事件”。
调用WSARecv函数
(1)如果系统之前已经接收了Socket数据:
函数返回值为0,函数的出参里已经有收到的Socket数据了,但是,阻塞等待的线程依然会收到事件,事件的参数里面也带有Socket数据。
(2)如果系统之前没有接收到Socket数据:
函数返回值不为0,如果WSAGetLastError为WSA_IO_PENDING时,表示投递该操作给系统成功,阻塞等待的线程会收到事件(该事件有可能表示真的收到了数据,也可能是收数据失败)。
5.3 WSASend操作
有可能收到“SOCKET发送数据完成事件”,“SOCKET断开连接事件”。
当收到“发送数据完成事件”,并不代表数据真的发送完成,可能实际上只发了一部分,对方只收到一部分。
6 实际模型
实际的服务器模型更加复杂,这个图还是简化了的,没有画上accept部分。
图中,网络模块和逻辑模块都属于用户层代码。
系统API模块有两个socket IO线程,一般来说,系统API层的线程数为:CPU数目*2。
网络模块有四个SOCKET工作线程。一般来说,用户层的SOCKET工作线程数为:CPU数目*4。