网路IO管理教程
网路IO,会涉及到两个系统对象,一个是用户空间调用IO的进程或则线程,另一个是内核
空间的内核系统,例如发生IO操作read时,它会经历两个阶段:
1.等待数据打算就绪
2.将数据从内核拷贝到进程或则线程中。
由于在以上两个阶段上各有不同的情况,所以出现了多种网路IO模型
服务器模型Reactor与Proactor
对高并发编程,网路联接上的消息处理,可以分为两个阶段:等待消息打算好、消息处
理。当使用默认的阻塞套接字时(比如前面提及的1个线程捆绑处理1个联接),常常是把
这两个阶段合而为一,这样操作套接字的代码所在的线程就得睡眠来等待消息打算好,这导
致了高并发下线程会频繁的睡眠、唤醒,进而影响了CPU的使用效率。
高并发编程方式其实就是把两个阶段分开处理。即,等待消息打算好的代码段,与处理
消息的代码段是分离的。其实,这也要求套接字必须是非阻塞的linux epoll 实现,否则,处理消息的代码段
很容易造成条件不满足时,所在线程又步入了睡眠等待阶段。这么问题来了,等待消息打算
好这个阶段如何实现?它其实还是等待,这意味着线程还是要睡眠的!解决办法就是,线程
主动查询,或则让1个线程为所有联接而等待!这就是IO多路复用了。多路复用就是处理
等待消息打算好这件事的,但它可以同时处理多个联接!它也可能“等待”,所以它也会造成
线程睡眠,但是这不要紧,由于它一对多、它可以监控所有联接。这样,当我们的线程被唤
醒执行时,就一定是有一些联接打算好被我们的代码执行了。
作为一个高性能服务器程序一般须要考虑处理三类风波:
I/O风波,定时风波及讯号。
两种高效的风波处理模型:Reactor和Proactor。
Reactor模型
首先来回想一下普通函数调用的机制:程序调用某函数,函数执行,程序等待,函数将
结果和控制权返回给程序,程序继续处理。Reactor释义“反应堆”,是一种风波驱动机制。
和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰
相反,Reactor逆置了风波处理流程,应用程序须要提供相应的插口并注册到Reactor上,
假如相应的时间发生qq linux,Reactor将主动调用应用程序注册的插口,这种插口又称为“回调函
数”。
Reactor模式是处理并发I/O比较常见的一种模式,用于同步I/O,中心思想是将所有要
处理的I/O风波注册到一个中心I/O多路复用器上,同时主线程/进程阻塞在多路复用器上;
一旦有I/O风波到来或是打算就绪(文件描述符或socket可读、写),多路复用器返回并将事
先注册的相应I/O风波分发到对应的处理器中。
Reactor模型有三个重要的组件:
多路复用器:由操作系统提供,在linux上通常是select,poll,epoll等系统调用。
风波分发器:将多路复用器中返回的就绪风波分到对应的处理函数中。
风波处理器:负责处理特定风波的处理函数。
具体流程如下:
1.注册读就绪风波和相应的风波处理器;
2.风波分离器等待风波;
3.风波到来,激活分离器,分离器调用风波对应的处理器;
4.风波处理器完成实际的读操作,处理读到的数据,注册新的风波,之后退还控制
权。
Reactor模式是编撰高性能网路服务器的必备技术之一,它具有如下的优点:
响应快,何必为单个同步时间所阻塞,尽管Reactor本身仍然是同步的;
编程相对简单,可以最大程度的防止复杂的多线程及同步问题,而且防止了多线程/进
程的切换开支;
可扩充性,可以便捷的通过降低Reactor实例个数来充分借助CPU资源;
可复用性,reactor框架本身与具体风波处理逻辑无关,具有很高的复用性;
Reactor模型开发效率上比起直接使用IO复用要高,它一般是单线程的,设计目标是
希望单线程使用一颗CPU的全部资源,但也有附送优点,即每位风波处理中好多时侯可以
不考虑共享资源的互斥访问。但是缺点也是显著的,现今的硬件发展,早已不再遵守摩尔定
律,CPU的频度受制于材料的限制不再有大的提高,而改为是从核数的降低上提高能力,
当程序须要使用多核资源时,Reactor模型都会惨剧,为何呢?
假如程序业务很简单,譬如只是简单的访问一些提供了并发访问的服务,就可以直接开
启多个反应堆,每位反应堆对应一颗CPU核心,这种反应堆上跑的恳求互不相关,这是完
全可以借助多核的。诸如Nginx这样的http静态服务器。
Proactor模型
具体流程如下:
1.处理器发起异步操作,并关注I/O完成风波
2.风波分离器等待操作完成风波
3.分离器等待过程中,内核并行执行实际的I/O操作,并将结果数据存入用户自定义缓冲
区,最后通知风波分离器读操作完成
4.I/O完成后,通过风波分离器呼唤处理器
5.风波处理器处理用户自定义缓冲区中的数据
从前面的处理流程,我们可以发觉proactor模型最大的特征就是Proactor最大的特征是
使用异步I/O。所有的I/O操作都交由系统提供的异步I/O插口去执行。工作线程仅仅负责
业务逻辑。在Proactor中,用户函数启动一个异步的文件操作。同时将这个操作注册到多路
复用器上。多路复用器并不关心文件是否可读或可写而是关心这个异步读操作是否完成。异
步操作是操作系统完成,用户程序不须要关心。多路复用器等待直至有完成通知到来。当操
作系统完成了读文件操作——将读到的数据复制到了用户原本提供的缓冲区以后,通知多路
复用器相关操作已完成。多路复用器再调用相应的处理程序,处理数据。
Proactor降低了编程的复杂度linux怎么查看系统版本,但给工作线程带来了更高的效率。Proactor可以在
系统态将读写优化,借助I/O并行能力,提供一个高性能单线程模型。在windows上,
因为没有epoll这样的机制,因而提供了IOCP来支持高并发,因为操作系统做了较好的
优化,windows较常采用Proactor的模型借助完成端口来实现服务器。在linux上,在
2.6内核出现了aio插口,但aio实际疗效并不理想,它的出现,主要是解决poll性能不
佳的问题,但实际上经过测试,epoll的性能低于poll+aio,但是aio不能处理accept,
因而linux主要还是以Reactor模型为主。
在不使用操作系统提供的异步I/O插口的情况下,还可以使用Reactor来模拟Proactor,
差异是:使用异步插口可以借助系统提供的读写并行能力,而在模拟的情况下,这须要在用
户态实现。具体的做法只须要这样:
1.注册读风波(同时再提供一段缓冲区)
2.风波分离器等待可读风波
3.风波到来,激活分离器,分离器(立刻读数据,写缓冲区)调用风波处理器
4.风波处理器处理数据,删掉风波(须要再用异步插口注册)
我们晓得,Boost.asio库采用的即为Proactor模型。不过Boost.asio库在Linux平台采用
epoll实现的Reactor来模拟Proactor,但是另外开了一个线程来完成读写调度。
同步I/O模拟Proactor模型
1.主线程往epoll内核风波表中注册socket上的读就绪风波。
2.主线程调用epoll_wait等待socket上有数据可读。
3.当socket上有数据可读时,epoll_wait通知主线程。主线程从socket循环读取数据,
直至没有更多数据可读,之后将读取到的数据封装成一个恳求对象并插入恳求队列。
4.睡眠在恳求队列上的某个工作线程被唤起,它获得恳求对象并处理顾客恳求,之后
往epoll内核风波表中注册socket上的写就绪风波。
5.主线程调用epoll_wait等待socket可写。
6.当socket可写时linux epoll 实现,epoll_wait通知主线程。主线程往socket上写入服务器处理顾客
恳求的结果。
两个模式的相同点,都是对某个IO风波的风波通知(即告诉某个模块,这个IO操作可
以进行或早已完成)。在结构上二者也有相同点:demultiplexor负责递交IO操作(异步)、
查询设备是否可操作(同步),之后当条件满足时,就回落注册处理函数。
不同点在于,异步情况下(Proactor),当反弹注册的处理函数时,表示IO操作早已完
成;同步情况下(Reactor),反弹注册的处理函数时,表示IO设备可以进行某个操作(can
readorcanwrite),注册的处理函数这个时侯开始递交操作。
Libevent,libev,libuv
libevent:名气最大,应用最广泛,历史悠久的跨平台风波库;
libev:较libevent而言,设计更简洁,性能更好,但对Windows支持不够好;
libuv:开发node的过程中须要一个跨平台的风波库,她们首选了libev,但又要支持
Windows,故重新封装了一套,linux下用libev实现,Windows下用IOCP实现;
优先级
libevent:激活的风波组织在优先级队列中,各种风波默认的优先级是相同的,可以通过设置
风波的优先级使其优先被处理
libev:也是通过优先级队列来管理激活的时间,也可以设置风波优先级
libuv:没有优先级概念,依照固定的次序访问各种风波
风波循环
libevent:event_base用于管理风波
libev:激活的风波组织在优先级队列中,各种风波默认的优先级是相同的,
libuv:可以通过设置风波的优先级使其优先被处理
线程安全
event_base和loop都不是线程安全的,一个event_base或loop实例只能在用户的一个线程
内访问(通常是主线程),注册到event_base或则loop的event都是串行访问的,即每位执
行过程中,会根据优先级次序访问早已激活的风波,执行其反弹函数。所以在仅使用一个
event_base或loop的情况下,反弹函数的执行不存在并行关系