UNIX网络I/O模型

阻塞I/O

         Socket设置为阻塞模式,当socket不能立即完成I/O操作时,进程或线程进入等待状态,直到操作完成。如下图:

         2009年5月29日 - 彭卫 - Tech-Oriented

         这种模型非常经典,也被广泛使用,优势在于非常简单,等待的过程中占用的系统资源微乎其微,程序调用返回时,必定可以拿到数据;

         但简单也带来一些缺点,程序在数据到来并准备好以前,不能进行其他操作,需要有一个线程专门用于等待,这种代价对于需要处理大量连接的服务器而言,是很难接受的;

 

非阻塞I/O

         socket设置成非阻塞模式,与阻塞模式不同的是:无数据时,也不会进入等待,而是立即返回特定错误,如下图:

         2009年5月29日 - 彭卫 - Tech-Oriented

这种模式在没有数据可以接收时,可以进行其他的一些操作,比如有多个socket时,可以去查看其他socket有没有可以接收的数据;

实际应用中,这种I/O模型的直接使用并不常见,因为它需要不停的查询,而这些查询大部分会是无必要的调用,白白浪费了系统资源;

非阻塞I/O应该算是一个铺垫,为I/O复用和信号驱动奠定了非阻塞使用的基础。

 

I/O复用

         I/O复用模型能让一个或多个socket可读或可写准备好时,应用能被通知到;I/O复用模型早期用select实现,它的工作流程如下图:

    2009年5月29日 - 彭卫 - Tech-Oriented

         这种模型的使用场景一般有这样一个共同特点:都有多个socket需要处理,这样能在获取I/O事件时复用同一个等待机制。比如监听服务器,既要处理监听的socket,又要处理连接的socket

         I/O复用是应用场景较多的一种模式,socket连接数多时,大多会采用它。除了select以外,I/O复用的还可以用pollepollkqueuefreebsd)来实现,后两者在处理大量连接时性能上有很大的提高。

 

信号驱动

         信号驱动模型是在socket准备好的时候用信号的方式进行通知,然后应用程序从内核读取数据。

         然而,对于socketSIGIO触发意味着多种可能,对于UDP有两种,对于TCP,则有7种,要想区分是何种操作引起的signal都是一件困难的事情,所以这种模型很少被实用,直到内核2.3起,引入了POSIX RT-Signal机制以后,这一现象得到些许改善。

        2009年5月29日 - 彭卫 - Tech-Oriented

 

异步I/O

         在标准Unix下,异步I/O是由“aio_XXX”接口 提供的,它把一个信号和值与每一个I/O操作关联起来。异步I/OPOSIX 1003.1b实时标准的扩展,也属于Single Unix Specificationversion 2

         几年前,Ben LaHaise实现了Linux AIO,合并到了2.5.32的内核中,在2.6时它正式成为标准特性。然而,令人遗憾的是,它目前还不支持对socket的操作,相信不久以后会完善起来。

         异步I/O的模型与I/O复用和信号驱动颇有些相似,但最大的区别是:信号到达时,I/O操作已经由内核完成,应用只需要继续处理数据就好;

         POSIXAIO的操作流程如下:

 2009年5月29日 - 彭卫 - Tech-Oriented

Windows网络I/O模型

         WindowsI/O模型与UNIX有一些类似的地方,差异也少:

         阻塞I/O、非阻塞I/O I/O复用使用起来基本一样;

windowssignal不支持SIGIO,这样也就没有了基于signal的信号驱动模型,不过windows利用自身的窗口句柄和Event也做出了类似的东西,它们分别是WSAAyncSelectWSAEventSelect

         对于异步I/Owindows已经有了自己的解决方案,并且支持对socket的操作,那就是是IOCP

WSAAyncSelectWSAEventSelect

         WSAAyncSelect用窗口句柄和自定义的消息,来传递socket事件,典型的windows处理逻辑。当READWRITEACCEPT等事件发生时,与Socket句柄绑定的窗口会收到指定的消息,消息的参数可以判断发生了什么样的网络事件;

         WSAEventSelectWSAAyncSelect不同,它不用窗口而用Event来传递socket事件,同时用WSAWaitForMultipleEventsEvent等待到Event事件,再通过WSAEnumNetworkEvents获取到底有哪些网络事件发生了;需要注意的是:由于系统的限制,WSAWaitXXX函数里waitEvent最多只能有64个。

         两者的相同之处在于,收到网络事件以后,它们还需要主动去操作数据;

         性能上,使用Event来通知肯定更为迅速。

 

Overlapped I/O

         Windows下的异步IOIOCP的基础,就像非阻塞I/OI/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

IOCP

         采用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并发量较大时,效率更高;

 

多路复用模型(Multiplexing

         多路复用技术可以用来提高网络I/O的使用效率,是所有大中型网络框架都必须使用的技术手段,I/O复用实现、异步I/O都可以实现多路复用模型;

         ACE提出了两种经典的多路复用模型:ProactorReactor

Reactor

Reactor一般用I/O复用实现,其工作流程如下图:

         网络编程模型综述 - 彭卫 - Tech-Oriented

1.       注册读就绪事件和相应的事件处理器;

2.       事件分离器等待事件;

3.       事件到来,激活分离器,分离器调用事件对应的处理器;

4.       事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。

 

Proactor

Proactor一般用异步I/O实现;与reactor不同的是,处理器不关心I/O就绪事件,它关注的是完成事件;

下图是Proactor的流程:

网络编程模型综述 - 彭卫 - Tech-Oriented

1.       处理器发起异步操作,并关注I/O完成事件;

2.       事件分离器等待操作完成事件;

3.       分离器等待过程中,内核并行执行实际的I/O操作,并将结果数据存入用户自定义缓冲区,最后通知事件分离器读操作完成;

4.       I/O完成后,通过事件分离器呼唤处理器;

5.       事件处理器处理用户自定义缓冲区中的数据;

 

 

线程(进程)模型

单线程模型

         单个线程采用多路复用技术管理所有socketsocket全部设置为non-blocking模式,由事件通知触发网络读写;

在处理大量连接时,是非常经典的线程模型之一;

         这种模型工作方式如下图所示:

          2009年5月29日 - 彭卫 - Tech-Oriented

需要注意的是,由于所有的socket操作都在一个线程中完成,所以必须保证在线程中,除了网络I/O操作以外,没有其他引发阻塞的调用。

 

多线程模型

每个连接对应一个线程

         一个网络socket对应一个处理线程,socket采用阻塞I/O模型;

         这种模型是小程序和java常用的策略,对于交互式的长连接应用也是常见的选择(比如BBS),也常用来做内部服务器交互的模型。 这种策略很难满足高性能程序的需求,好处是实现极其简单,容易嵌入复杂的交互逻辑。Apacheftpd等都是这种工作模式。

线程池

线程池一般有两种模式:Half-Sync/Half-Async模式和Leader/Followers模式

半同步、半异步模式(Half-Sync/Half-Async

         这种模式有三部分组成:异步事件接收层、事件同步队列、同步事件处理层;

         其中,异步事件接收层为一个线程,同步事件处理层可以有多个线程;

         它的工作流程很清晰:

1.       异步线程负责检查网络的异步事件;

2.       发生网络事件时,异步线程把网络事件放入事件队列;

3.       同步线程从队列中获取网络事件,并执行同步的读或写操作;

 2009年5月29日 - 彭卫 - Tech-Oriented

这个过程需要注意的是,不要引起两个同步线程同时接收或发送一个socket的情况。

 

领导者、追随者模式(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的工作模式,当线程1GetQueuedCompletionStatus这里返回后,如果线程1的处理过程没有超过某个时间段,而是很快就返回,之后继续GetQueuedCompletionStatus,那OS会让新到的数据从线程1GetQueuedCompletionStatus获取,这样就减少了线程的CONTEXT切换代码;反之,如果线程1处理时间比较长,那么新到的数据将会由线程2GetQueuedCompletionStatus获得;

 

 

多进程模型

         一个客户端对应一个进程来处理,也是一种历史悠久的网络模型,linux的典型例子就是inetd服务。这种方式用来处理间断性内部数据处理时,比其常驻内存的stand-alone模式更节省系统资源。

 

成熟的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中提出了两种网络模式:ProactorReactor

ASIO

C++I/O框架,逐渐成为Boost库的一部分。it’s like ACE updated for the STL era。”

支持selectepollIOCPIO模型;

libevent

Niels ProvosC编写的一个轻量级的I/O框架。它支持kqueueselectpollepoll

1.4.11版还不支持windowsIOCP,但已经有很多开发者自己修改源码,把IOCP合并进去。

 

 

 

C10K问题与解决之道

         一台服务器如何同时处理一万个以上的客户端,这就是著名的C10K问题;这个问题曾经困扰过很多服务器的架构师,但这种困扰随着时间的推移早已成为了过去。

         网络的众多模型中,有些适合简单处理,有些适合复杂应用,而C10K问题考验的则是网络框架的并发和大连接处理能力,异步I/O虽然也是为并发和大连接设计,但目前aio并不支持socket,所以目前最适合的解决方案是I/O复用。

         I/O复用最初在解决C10K问题时也并不出色,随着连接数的增加,处理单个socket请求所花费的事件也迅速增加,但kqueueepoll的出世后,I/O复用成为了C10K问题的首选方案,下图是libevent给出的测试数据:

      网络编程模型综述 - 彭卫 - Tech-Oriented

从图中可以看出,selectpoll随着需要处理的连接数(横轴)增多,处理单个连接的时间明显变长,而epollkqueue的表现则非常优秀。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值