在几年前曾经作过一个网络项目,当时对网络通讯仅仅是有点基础。tcp/ip协议的基础还算不错,sockt的应用看起来也不算复杂。因而就用异步非阻塞的sockt通讯实现了服务器端和客户端。可是项目在联合调试阶段就出现了重大的性能问题。项目的服务器端同时连入的链接数在几百左右,而服务器端的资源消耗很是厉害。就是在这样的环境下,第一次接触到高效通讯模型这个概念,IOCP完成端口 (I/O Completion Port)也是在这个时候成为了我心目中windows平台下这个概念的最高峰,同时也成为了个人最大困惑。
做为当时菜虫级的程度,找遍了网络和书店以及msdn,找到了大量关于IOCP的资料,但愿能了解它的原理和应用。遗憾的是,一开始就先入为主的认为IOCP是一种网络通讯模型,而走入了误区(都是“端口”惹的祸...),更可悲的是在找到的资料中混杂了“重叠端口”(重叠I/O的另外一种翻译...找不到具体说法,只是经过代码发现是一回事),后来又有“重叠I/O”、“完成例成(completion routines)”、“select模式的重叠端口”等类似名称的资料,终于最后在海量的资料里面迷失了。都怪本身e文太烂啊。。。。c++
理一理这样的一批概念:windows
同步、异步、阻塞、非阻塞
select模式
重叠I/O(Overlapped I/O)
完成例成(completion routines)
IOCP完成端口 (I/O Completion Port)服务器
上面的概念我不知道该用什么来统称它们,只能说它们都和网络开发相关。比较混乱的说,而写这个文章的目的也是想理一理这些概念,作作头脑风暴。网络
同步、异步、阻塞、非阻塞是IO的基本原理。同步和异步是针对功能的执行顺序来讲的,而阻塞和非阻塞是针对等待IO数据的方式说的。所以这是两对概念,同步与阻塞,异步与非阻塞都没有必然的联系。通俗的说,同步就是工做线程在处理IO时等待IO完成再继续后面的工做;异步就是工做线程不等待IO处理的结果就继续后面的工做,而IO处理结果将经过回调方式返回;阻塞是在等待IO时,若是IO没有可用数据或数据没有传送完成,那么一直等待下去,直到IO处理完数据再返回;非阻塞就是无论IO是否有可用数据或数据已经传送,照样返回。只要把同步和阻塞分清,这四个概念就很容易理清了。数据结构
同步、异步、阻塞、非阻塞等原理肯定了网络通讯的基本网络通讯模型结构。它们处理的怎么等待数据和怎么收发数据的问题,可是对通讯性能并无提供更多的指导。特别是服务端,当成千上万的链接发生并发时,下降cpu的占用率,减小内存使用率,减小带宽就成为了很关键的问题。所以便有先有了重叠I/O(Overlapped I/O)。并发
重叠I/O(Overlapped I/O)是怎么一回事呢?
若是应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。而阻塞、select、WSAAsyncSelect以及WSAEventSelect等4种模型种,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知能够读入的容量。当应用程序调用接收函数以后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差异就体现出来了。
我想之因此叫重叠也就是由于它是两个缓冲重叠的意思。就这么一重叠就剩了很多空间和很多事情。app
提重叠I/O(Overlapped I/O)以前应该先提select模式,由于它是最接近同步、异步、阻塞、非阻塞模式的一种模式。select模式、WSAAsyncSelect以及WSAEventSelect都是经过轮询socket列表来肯定那个socket是当前有效的(它们的原理仍是有点不同的)。好处是防止在在阻塞模式的套接字里被锁死,避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。
也就是说,select模式提供了一种比较优秀的查询方式来判断当前链接的状态,避免了直接使用阻塞或者非阻塞所致使的问题。异步
提重叠I/O(Overlapped I/O)提供了比select模式更优秀的缓冲管理,那它是否是也提供了比select更优秀的轮询方式呢?能够说是,也能够说不是。由于提重叠I/O(Overlapped I/O)并无直接提供这样的方法,而是提供了一种手段。这手段是在Overlapped数据结构里标记了该数据所属的socket和有数据到的来。怎么捕获这个事件?那就是完成例成(completion routines)和事件对象通知(event object notification) 所干的事情了。完成例成(completion routines)相似于dotnet里面的异步,在socket接收数据时给WSARecv传送一个完成后回调的函数句柄,来达到捕获数据到来的事件;至于事件对象通知(event object notification) 利用的是Overlapped数据结构里包含“有数据到来的事件”的WSAEVENT hEvent成员与事件句柄关联实现的,这个就象是使用了类为外部提供事件通知的方式。socket
使用事件或者是回调都要比轮询高效。所以重叠I/O(Overlapped I/O)能够支持很高的链接数,传说能够上万。可是重叠I/O(Overlapped I/O)也有不足的地方。重叠缓冲是很好的作法,可是对应每一个socket都要有一个缓冲,那么一万个链接就会有一万个缓冲了。另外还有就是不管是异步仍是事件来唤醒线程,都和缓冲存在一个链接一个线程的状况。不面对大量的并发,这两个问题并不明显,可是若是并发链接数量达到某个级别,cpu和内存都将存在严重的浪费。并且其支持的链接数与硬件性能的比只能是一个固定值。tcp
为了解决重叠I/O的问题,便有了IOCP完成端口 (I/O Completion Port)。为了解决大量并发产生的资源浪费,完成端口引入了相似线程池和资源自动分配回收的两种技术(这里之因此说是相似,是由于对于IOCP的原理尚未做更深刻的研究说不许其准确的原理,只能经过它的现象来认为那是线程池+高效调度+资源回收综合起来的)。所谓的大量并发链接不是指同一时间内的链接数量,而是在某时间区间内的链接数量。所以在该时间区间内并非全部的链接线程都是活动的,只要具备高效的线程分配和调度,那么在每一个时刻提供比整个区间内链接线程小得多的活动线程也能把该时间区间内的并发处理,这就是线程池的好处。对于大量的并发短链接,其缓冲绝对是可重复使用的,其线程也是可重复使用的,所以准确高效的资源回收能是资源最大效率的运用起来。经过对线程和缓冲的有效再利用使完成端口在更小的资源环境下取得比重叠I/O更好的性能。
完成端口这个名字使很多人迷惑,咱们彻底能够经过它的原理和应用来理解这个名字。完成端口是经过调度线程和提供资源重用来取得高效的,所以完成端口把链接的调度和其缓冲的分配都进行了封装,提供了很容易使用的接口。我想完成端口的“完成”即是这样而来--调度和资源分配的自动完成,“端口”--就是指IO端口,每一个IO设备都有其本身的IO号,这个IO号又被成为IO的端口。那英文名里面的I/O又是什么回事呢?这和重叠I/O是同样的,就是指它们是应用在I/O上的一种技术。广义的说,网络不也是IO?
重叠I/O和IOCP完成端口确实是能够用在全部的IO应用上。而完成端口由于封装了调度和回收机制,还能够把它看成高效的队列处理技术和线程调度技术来使用。
这是我对这些概念的理解,可能理解得并不许确。上面说起的网络模型都是在windows平台上提供了API支持的,能找到的示例代码大部分也是c++的代码。在接触完成端口时的想法是如何把它用在dotnet上,所以在研究完成端口的原理和应用时,也就一直在思考可否在dotnet上使用这么一个高效的模型。固然不只仅是完成端口,select模式,重叠端口等在dotnet上也是少见的。所以后面就这个方向进行一下探讨。
另外,CodeProject上有个托管环境下的IOCP例子很值得研究。
Managed I/O Completion Ports (IOCP)
Managed I/O Completion Ports (IOCP) - Part 2待续....