Windows下的网络编程,IOCP(IO Completion Port)作为windows下效率最好的网络编程模型,可能是必须了解和掌握的知识。要理解IOCP,也必须了解有关网络编程的方方面面的知识。

有很长时间没有做网络编程方面的工作,因为需要,必须重新捡起来这方面的知识,同时整理成章。

作为随笔,相对比较杂乱,更多注重理解领会,文字多些,而代码少些。

一,相关知识:阻塞与非阻塞(blocking-Non-blocking),同步与异步(synch-asynch),重叠(overlapped)
网络编程涉及到许多方面的知识,而进程调度,IO管理,TCP/IP模型,多线程编程,这些只是作为网络编程的基础知识,在这里不再谈及。
作为网络编程的最重要的几个概念,在这里重新梳理一下。

※ 同步和异步
同步和异步的概念更多见于通信领域, 例如在串行通信中有定义:
同步通信是一种连续串行传送数据的通信方式,一次通信只传送一帧信息,帧信息包括n位。异步通信在发送字符时,所发送的字符之间的时间间隔可以是任意的,但需要在每一个字符的开始和结束的地方加上开始位和停止位。在这里,同步通信要求接收端时钟频率和发送端时钟频率一致,简单来说为的是不发生数据的丢失和错位。异步则因为有开始和停止位,则在接收端可以提前做好准备了,显然同步通信的效率要高。至此,我们可以简单的理解同步为 时钟频率一致,双方步调一致的工作。回过头来看在网络编程中的同步与异步,与通信的中概念有所不同。同步是指当线程开始IO操作后即进入等待,直到IO完成再进行下一步的工作。异步则是线程发出IO请求后将IO工作交给内核,不进行等待转而处理其他工作,直到系统内核通知线程IO完成。其根本在于是否等待。与串行通信同步的异同之处可以揣摩一二。对于IO来说,同步的效率要高,而对于整个系统来说,显然异步的系统利用率会更好。下图很形象的描述了同步和异步的过程。BTW,所谓重叠IO,通常就是指的异步IO。

网络编程与IOCP_第1张图片
本图来自于:
https://docs.microsoft.com/zh-cn/windows/desktop/FileIO/synchronous-and-asynchronous-i-o

(WaitForSingleObject -> NtWaitForSingleObject -> KeWaitForSingleObject,在这里有些源码,没有资料不大理解,只能略窥原理。https://www.xuebuyuan.com/856418.html)

※ 阻塞和非阻塞
很长一段时间,我一直没搞懂同步和阻塞究竟区别在哪里。
来看下对书上阻塞/非阻塞概念的理解(这里参考Unix网络编程卷1-6.2章I/O模型)。
书中将输入操作分为两个步骤:1,等待数据准备好,2,从内核将数据拷贝到进程。以UDP为例,recvfrom视为系统调用,不论recvfrom是如何实现的,通常在调用这个函数时都由应用程序进入到系统内核,待内核将数据准备好,然后在稍后某个时间点返回到应用程序。这时我们称我们的进程从recvfrom开始调用直到返回的整个时间段阻塞了。当recvfrom成功返回,我们的应用程序就可以处理数据报了(接收到数据)。(此即同步阻塞模型)
当我们将一个socket设置为非阻塞时,等于在告诉内核,我请求的IO操作不能完成时,不要让进程sleep,而是返回一个error。如果想要知道内核何时将数据准备好,只有通过不断调用recvfrom来询问内核,直到某一次recvfrom返回成功。(此即同步非阻塞模型)
将以上的概念与同步异步放在一起来理解,粗一看,都讲的是IO请求之后,等待与否。细细分辨,差别有二。
1,其实同步异步的概念更要大些,广泛些,例如线程同步,读写同步之类。就IO模型来说,以上面讲到的输入操作为例,同步异步最大的区别在于,同步是将请求交给内核,内核将数据准备好,即告诉进程,由进程来处理数据IO。而异步则将请求交给内核后,全部工作由内核完成。数据IO结束后才通知进程。
2,我们可以认为,阻塞仅仅是指调用recv,send等IO函数,函数是否立即返回的行为。不论这些函数是否立即返回,其原则上都是同步模型,区别在于等待内核返回和循环去询问内核是否OK。而异步则完全不需要去关心内核的状态,只需要安心坐等内核完成后的通知就可以了(当然这个等待往往也是一个阻塞的线程来完成这个任务,只不过IO线程并不阻塞)。
由阻塞和同步排列组合出来四种模型,同步阻塞(常用),同步非阻塞(多次查询效率低),异步阻塞(少见,我还难以理解这种实现),异步非阻塞(效率最高,IOCP即为此种)

顺便在提下 select模型(IO复用)
通过以上的介绍,我们应该很容易理解select模型了。select模型应该是一种同步阻塞模型,只不过阻塞的位置在select函数,而不是在IO函数。因为select是可以同时等待多个socket的状态,所以较简单的阻塞模型效率要高。事实上同时IO的socket不是太多的话,使用多线程,每个线程使用阻塞模型来等待一个socket的状态,可以达到select模型类似的效果。

Reactor与Proactor模式

参考文章:https://www.cnblogs.com/talenth/p/7068392.html
Unix网络编程卷1(by W.Richard Stevens)