IO模型介绍

一般情况而言,常用的五种IO Model为(参考:Richard Stevens--“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ):

IO模型 中文名称
blocking IO 阻塞IO
nonblocking IO 非阻塞IO
IO multiplexing IO多路复用
signal driven IO 信号驱动IO(少用)
asynchronous IO  异步IO

本次重点:IO多路复用,有兴趣的同学想要专研,可以了解这本书。

selectors模块

I/O多路复用是用于提升效率,单个进程可以同时监听多个网络连接IO。通过一种机制,可以监视多个文件描述符,一旦描述符就绪(读就绪和写就绪),能通知程序进行相应的读写操作。IO多路复用避免阻塞在IO上,原本为多进程或多线程来接收多个连接的消息变为单进程或单线程保存多个socket的状态后轮询处理。

下面我们来了解实现IO复用中的三个API(select、poll和epoll)的区别和联系。

select,poll,epoll都是IO多路复用的机制,I/O多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知应用程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。三者的原型如下所示:

select

  • int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

select的第一个参数nfds为fdset集合中最大描述符值加1,fdset是一个位数组,其大小限制为__FD_SETSIZE(1024),位数组的每一位代表其对应的描述符是否需要被检查。第二三四参数表示需要关注读、写、错误事件的文件描述符位数组,这些参数既是输入参数也是输出参数,可能会被内核修改用于标示哪些描述符上发生了关注的事件,所以每次调用select前都需要重新初始化fdset。timeout参数为超时时间,该结构会被内核修改,其值为超时剩余的时间。

select的调用步骤如下:

  1. 使用copy_from_user从用户空间拷贝fdset到内核空间;
  2. 注册回调函数__pollwait;
  3. 遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll);
  4. 以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数;
  5. __pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll 来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数 据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了;
  6. poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值;
  7. 如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是 current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout 指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd;
  8. 把fd_set从内核空间拷贝到用户空间。

select的缺点:

  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
  • select支持的文件描述符数量太小了,默认是1024。

poll

  • int poll(struct pollfd *fds, nfds_t nfds, int timeout)

poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。

poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。

epoll

  • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。

epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll 和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定 EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝 一次。   

对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在 epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调 函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用 schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。   

对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子, 在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

总结:

  • select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用 epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在 epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的 时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间,这就是回调机制带来的性能提升。
  • select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要 一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内 部定义的等待队列),这也能节省不少的开销。

这三种IO多路复用模型在不同的平台有着不同的支持,而epoll在windows下就不支持,好在我们有selectors模块,帮我们默认选择当前平台下最合适的。

实例:基于selectors模块实现聊天

服务端:

from socket import *
import selectorssel = selectors.DefaultSelector()def accept(server_fileobj, mask):conn, addr = server_fileobj.accept()sel.register(conn, selectors.EVENT_READ, read)def read(conn, mask):try:data = conn.recv(1024)if not data:print('closing', conn)sel.unregister(conn)conn.close()returnconn.send(data.upper() + b'_SB')except Exception:print('closing', conn)sel.unregister(conn)conn.close()server_fileobj = socket(AF_INET, SOCK_STREAM)
server_fileobj.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server_fileobj.bind(('127.0.0.1', 8088))
server_fileobj.listen(5)
server_fileobj.setblocking(False)  # 设置socket的接口为非阻塞
sel.register(server_fileobj, selectors.EVENT_READ,accept)  # 相当于网select的读列表里append了一个文件句柄server_fileobj,并且绑定了一个回调函数acceptwhile True:events = sel.select()  # 检测所有的fileobj,是否有完成wait data的for sel_obj, mask in events:callback = sel_obj.data  # callback=accpetcallback(sel_obj.fileobj, mask)  # accpet(server_fileobj,1)

客户端:

from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8088))while True:msg=input('>>: ')if not msg:continuec.send(msg.encode('utf-8'))data=c.recv(1024)print(data.decode('utf-8'))

结果:(略)

Python基础之IO多路复用相关推荐

  1. day36 python学习gevent io 多路复用 socketserver *****

    ---恢复内容开始--- gevent 1.切换+保存状态 2.检测单线程下任务的IO,实现遇到IO自动切换 Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在geven ...

  2. python网络编程——IO多路复用之epoll

    什么是epoll epoll是什么?在linux的网络编程中,很长的时间都在使用select来做事件触发.在linux新的内核中,有了一种替换它的机制,就是epoll.当然,这不是2.6内核才有的,它 ...

  3. 网络编程—IO多路复用详解

    假如你想了解IO多路复用,那本文或许可以帮助你 本文的最大目的就是想要把select.epoll在执行过程中干了什么叙述出来,所以具体的代码不会涉及,毕竟不同语言的接口有所区别. 基础知识 IO多路复 ...

  4. python -- IO多路复用

    python之路--IO模型 阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) ...

  5. Python网络编程:IO多路复用

    io多路复用:可以监听多个文件描述符(socket对象)(文件句柄),一旦文件句柄出现变化,即可感知. 1 sk1 = socket.socket() 2 sk1.bind(('127.0.0.1', ...

  6. IO多路复用select/poll/epoll详解以及在Python中的应用

    IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...

  7. python io多路复用_python实现IO多路复用 --- selector

    IO多路复用 O多路复用技术是使用一个可以同时监视多个IO阻塞的中间人去监视这些不同的IO对象,这些被监视的任何一个或多个IO对象有消息返回,都将会触发这个中间人将这些有消息IO对象返回,以供获取他们 ...

  8. Python异步非阻塞IO多路复用Select/Poll/Epoll使用

    来源:http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理 ...

  9. python io多路复用_Python之IO多路复用

    一.IO模型介绍 ​ 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这 ...

最新文章

  1. 百万人学AI:CSDN重磅共建人工智能技术新生态
  2. NLayerAppV3-Distributed Service Layer(分布式服务层)
  3. KD-tree的原理以及构建与查询操作的python实现
  4. Qt Linguist 翻译
  5. 群晖服务器有多少个硬盘,群晖新款NAS发布 采用16个硬盘位、最高支持192TB容量...
  6. 使用过滤统计信息解决基数预估错误
  7. ztree 标准得json数据格式_酷站推荐 - json-c.github.io/json-c - json-c API
  8. XP无法建立宽带连接的解决方法
  9. Ubuntu下ffmpeg 捕获屏幕和采集声卡、摄像头、麦克风声音
  10. 框架源码专题:Spring是如何集成Mybatis的?Spring怎么管理Mapper接口的动态代理
  11. Ofbiz架构讲解与讨论(crud)
  12. AD18等长线、蛇形线的设置
  13. Photoshop教程一:精细选择工具
  14. 乐优商城_第5章_-vue入门
  15. t420i升级固态硬盘提升_旧电脑升级!使用固态硬盘必做的5件事,让win10操作流畅如win7...
  16. 项目管理 | 项目资源管理(一)
  17. latex表格水平垂直居中
  18. 二十九、进阶之项目数据请求
  19. Altium Designer使用方法(DRC警告设置,走线技巧,DIY电路板打印技术)
  20. STM32实现串口下载

热门文章

  1. AHB lite协议
  2. 第一章 电商秒杀商品回顾
  3. docker访问宿主机MySQL
  4. linux没有应用程序,Ninite为Linux安装多个应用程序没有任何麻烦 | MOS86
  5. JavaScript 中两个 !! 是什么意思?
  6. pppd详解_【求解】pptpd 619错误详解
  7. 5G无线技术基础自学系列 | 毫米波和可见光
  8. db2服务器执行sql文件格式,db2数据库导出文件的类型
  9. CCF认证2018124-数据中心
  10. 我发现买不起自己出版的书了,这到底是咋回事?