部分图自己重新的画了一下,原来的看不很清楚
加了一个LF线程池的图
我的另外一篇关于网络的文章:
http://blog.chinaunix.net/u2/61062/showart.php?id=2138339
1.UNIX网络I/O模型
1.1阻塞I/O
Socket设置为阻塞模式,当socket不能立即完成I/O操作时,进程或线程进入等待状态,直到操作完成。如下图:
这种模型非常经典,也被广泛使用,优势在于非常简单,等待的过程中占用的系统资源微乎其微,程序调用返回时,必定可以拿到数据;
但简单也带来一些缺点,程序在数据到来并准备好以前,不能进行其他操作,需要有一个线程专门用于等待,这种代价对于需要处理大量连接的服务器而言,是很难接受的;
1.2非阻塞I/O
把socket设置成非阻塞模式,与阻塞模式不同的是:无数据时,也不会进入等待,而是立即返回特定错误,如下图:
这种模式在没有数据可以接收时,可以进行其他的一些操作,比如有多个socket时,可以去查看其他socket有没有可以接收的数据;
实际应用中,这种I/O模型的直接使用并不常见,因为它需要不停的查询,而这些查询大部分会是无必要的调用,白白浪费了系统资源;非阻塞I/O应该算是一个铺垫,为I/O复用和信号驱动奠定了非阻塞使用的基础。
1.3 I/O复用
I/O复用模型能让一个或多个socket可读或可写准备好时,应用能被通知到;I/O复用模型早期用select实现,它的工作流程如下图:
这种模型的使用场景一般有这样一个共同特点:都有多个socket需要处理,这样能在获取I/O事件时复用同一个等待机制。比如监听服务器,既要处理监听的socket,又要处理连接的socket。
I/O复用是应用场景较多的一种模式,socket连接数多时,大多会采用它。除了select以外,I/O复用的还可以用poll、epoll、kqueue(freebsd)来实现,后两者在处理大量连接时性能上有很大的提高。
1.4信号驱动
信号驱动模型是在socket准备好的时候用信号的方式进行通知,然后应用程序从内核读取数据。
然而,对于socket,SIGIO触发意味着多种可能,对于UDP有两种,对于TCP,则有7种,要想区分是何种操作引起的signal都是一件困难的事情,所以这种模型很少被实用,直到内核2.3起,引入了POSIX RT-Signal机制以后,这一现象得到些许改善。
1.5异步I/O
在标准Unix下,异步I/O是由 提供的,它把一个信号和值与每一个I/O操作关联起来。异步I/O是POSIX 1003.1b实时标准的扩展,也属于Single Unix Specification,version 2。
几年前,Ben LaHaise实现了Linux AIO,合并到了2.5.32的内核中,在2.6时它正式成为标准特性。然而,令人遗憾的是,它目前还不支持对socket的操作,相信不久以后会完善起来。
异步I/O的模型与I/O复用和信号驱动颇有些相似,但最大的区别是:信号到达时,I/O操作已经由内核完成,应用只需要继续处理数据就好;
POSIX的AIO的操作流程如下:
2.Windows网络I/O模型
Windows中I/O模型与UNIX有一些类似的地方,差异也少:
阻塞I/O、非阻塞I/O、 I/O复用使用起来基本一样;
windows的signal不支持SIGIO,这样也就没有了基于signal的信号驱动模型,不过windows利用自身的窗口句柄和Event也做出了类似的东西,它们分别是WSAAyncSelect和WSAEventSelect;
对于异步I/O,windows已经有了自己的解决方案,并且支持对socket的操作,那就是是IOCP;
2.1WSAAyncSelect与WSAEventSelect
WSAAyncSelect用窗口句柄和自定义的消息,来传递socket事件,典型的windows处理逻辑。当READ、WRITE、ACCEPT等事件发生时,与Socket句柄绑定的窗口会收到指定的消息,消息的参数可以判断发生了什么样的网络事件;
WSAEventSelect与WSAAyncSelect不同,它不用窗口而用Event来传递socket事件,同时用WSAWaitForMultipleEventsEvent等待到Event事件,再通过WSAEnumNetworkEvents获取到底有哪些网络事件发生了;需要注意的是:由于系统的限制,WSAWaitXXX函数里wait的Event最多只能有64个。
两者的相同之处在于,收到网络事件以后,它们还需要主动去操作数据;
性能上,使用Event来通知肯定更为迅速。
2.2Overlapped I/O
Windows下的异步IO,IOCP的基础,就像非阻塞I/O是I/O复用的基础一样;
重叠I/O的数据结构体如下,注意其中有一个Event对象
typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED;
在创建socket时,声明该socket为重叠模式,此时需创建一个WSAOVERLAPPED对象,当调用发送或者接收时,把这个对象传入;
然后,可以等待WSAOVERLAPPED对象中的Event来获知网络事件,这个过程与WSAEventSelect有些类似,与之不同的是要使用WSAGetOverlappedResult来获取重叠IO返回的结果(注意,这里获取的是结果,也就是说当Event触发时,它已经把数据准备好了,是异步IO的处理方法);
虽然Overlapped I/O已经是异步IO,但它在处理大量并发请求时比WSAEventSelect好不了多少,这样就有了IOCP。
2.3IOCP
采用Overlapped I/O的一个异步I/O框架,它的优势是操作大量句柄时效率更高;
IOCP允许多个socket绑定到一个完成端口(可理解为比较特别的句柄)上,同时,在线程池的工作线程中监听完成端口,以此来获得这些文件句柄的IO操作结果;
注意,这里不是获取IO操作的事件,而是结果,典型的异步I/O模式;
IOCP有这样一些特性:
1.一个完成端口可绑定多个文件句柄;
2.多个线程同时访问一个完成端口,没有线程安全问题;
3.一个线程最多只能关联一个完成端口,GetQueuedCompletionStatus调用时进行绑定,线程运行时可以换绑,绑定结束条件:换绑、线程退出、完成端口被释放;
4.完成端口创建时,有最大关联线程数,超过这个最大值的线程,会被block,直至关联线程数减少到最大值以下;
5.完成端口关联线程数量的取值可参考CPU核数,如果线程中处理数据的流程比较长,则应加大这个值;
6.异步IO完成前,线程等待的是完成端口,而不是某个文件句柄的异步IO操作;
7.一个异步IO完成时,系统会把完成数据(Complete Packet)放入一个FIFO队列;
8.一个异步IO完成时,最后一个wait的线程会被释放(LIFO),这样可以减少线程切换;
9.可以通过PostQueuedCompletionStatus发送完成数据包,这样可以做一些自己的扩展;
10.IOCP在线程逻辑简单,同时异步IO并发量较大时,效率更高;
3.多路复用模型(Multiplexing)
多路复用技术可以用来提高网络I/O的使用效率,是所有大中型网络框架都必须使用的技术手段,I/O复用实现、异步I/O实现都可以实现多路复用模型;
ACE提出了两种经典的多路复用模型:Proactor和Reactor;
3.1Reactor
Reactor一般用I/O复用实现,其工作流程如下图:
1.注册读就绪事件和相应的事件处理器;
2.事件分离器等待事件;
3.事件到来,激活分离器,分离器调用事件对应的处理器;
4.事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
3.2Proactor
Proactor一般用异步I/O实现;与reactor不同的是,处理器不关心I/O就绪事件,它关注的是完成事件;
下图是Proactor的流程:
1.处理器发起异步操作,并关注I/O完成事件;
2.事件分离器等待操作完成事件;
3.分离器等待过程中,内核并行执行实际的I/O操作,并将结果数据存入用户自定义缓冲区,最后通知事件分离器读操作完成;
4.I/O完成后,通过事件分离器呼唤处理器;
5.事件处理器处理用户自定义缓冲区中的数据;
4.线程(进程)模型
4.1单线程模型
单个线程采用多路复用技术管理所有socket,socket全部设置non-blocking模式,由事件通知触发网络读写;在处理大量连接时,是非常经典的线程模型之一;
这种模型工作方式如下图所示:
需要注意的是,由于所有的socket操作都在一个线程中完成,所以必须保证在线程中,除了网络I/O操作以外,没有其他引发阻塞的调用。
4.2多线程模型
4.2.1每个连接对应一个线程
一个网络socket对应一个处理线程,socket采用阻塞I/O模型;
这种模型是小程序和java常用的策略,对于交互式的长连接应用也是常见的选择(比如BBS),也常用来做内部服务器交互的模型。 这种策略很难满足高性能程序的需求,好处是实现极其简单,容易嵌入复杂的交互逻辑。Apache、ftpd等都是这种工作模式。
4.2.2线程池
线程池一般有两种模式:Half-Sync/Half-Async模式和Leader/Followers模式
4.2.2.1半同步、半异步模式(Half-Sync/Half-Async)
这种模式有三部分组成:异步事件接收层、事件同步队列、同步事件处理层;
其中,异步事件接收层为一个线程,同步事件处理层可以有多个线程;
它的工作流程很清晰:
1.异步线程负责检查网络的异步事件;
2.发生网络事件时,异步线程把网络事件放入事件队列;
3.同步线程从队列中获取网络事件,并执行同步的读或写操作;
这个过程需要注意的是,不要引起两个同步线程同时接收或发送一个socket的情况。
4.2.2.2领导者、追随者模式(Leader/Followers)
这种模式与Half-Sync/Half-Async完全不同,没有事件队列,没有固定的事件接收者,每个线程都是事件接收者,也是处理者;
Leader/Followers的流程:
1.准备若干个线程用来处理大量的事件;
2.有一个线程作为Leader,等待事件的发生;其他的线程作为Follower,仅仅是睡眠;
3.有事件需要处理时,如果Leader能很快处理掉,Leader会再次进入等待状态;
4.如果Leader不能马上处理完,Leader则从Followers中指定一个新的Leader,自己去处理事件,不再当Leader;
5.被唤醒的Follower作为新的Leader等待事件的发生;
6. 处理事件的线程处理完毕以后,就会成为Follower的一员,直到被唤醒成为Leader;
IOCP就是典型的L/F的工作模式,当线程1从GetQueuedCompletionStatus这里返回后,如果线程1的处理过程没有超过某个时间段,而是很快就返回,之后继续GetQueuedCompletionStatus,那OS会让新到的数据从线程1的GetQueuedCompletionStatus获取,这样就减少了线程的CONTEXT切换代码;反之,如果线程1处理时间比较长,那么新到的数据将会由线程2的GetQueuedCompletionStatus获得;
5.多进程模型
一个客户端对应一个进程来处理,也是一种历史悠久的网络模型,linux的典型例子就是inetd服务。这种方式用来处理间断性内部数据处理时,比其常驻内存的stand-alone模式更节省系统资源。
6.成熟的IO框架介绍
ACE
“重量级的C++ I/O框架,用面向对象实现了一些I/O策略和其它有用的东西,特别是它的Reactor是用OO方式处理非阻塞I/O,而Proactor是用OO方式处理异步I/O的( In particular, his Reactor is an OO way of doing nonblocking I/O, and Proactor is an OO way of doing asynchronous I/O). ”
从很多实际使用来看,ACE是一个很值得学习的网络框架,但由于它过于重量级,导致使用起来并不方便。
ACE中提出了两种网络模式:Proactor和Reactor。
ASIO
“C++的I/O框架,逐渐成为Boost库的一部分。it’s like ACE updated for the STL era。”
支持select、epoll、IOCP等IO模型;
libevent
由Niels Provos用C编写的一个轻量级的I/O框架。它支持kqueue和select、poll和epoll。
1.4.11版还不支持windows的IOCP,但已经有很多开发者自己修改源码,把IOCP合并进去。