IO多路复用

I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

select
 
它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
 
poll
 
poll它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
 
epoll
 
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
 

Python中的select模块专注于I/O多路复用,提供了select  poll  epoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kqueue方法(freeBSD系统)

select方法:

进程指定内核监听哪些文件描述符(最多监听1024个fd)的哪些事件,当没有文件描述符事件发生时,进程被阻塞;当一个或者多个文件描述符事件发生时,进程被唤醒。fd:file descriptor 文件描述符。

当我们调用select()时:

  1 上下文切换转换为内核态

  2 将fd从用户空间复制到内核空间

  3  内核遍历所有fd,查看其对应事件是否发生

  4  如果没发生,将进程阻塞,当设备驱动产生中断或者timeout时间后,将进程唤醒,再次进行遍历

  5 返回遍历后的fd

  6  将fd从内核空间复制到用户空间

fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout])

参数: 可接受四个参数(前三个必须)
rlist: wait until ready for reading
wlist: wait until ready for writing
xlist: wait for an “exceptional condition”
timeout: 超时时间 返回值:三个列表 select方法用来监视文件描述符(当文件描述符条件不满足时,select会阻塞),当某个文件描述符状态改变后,会返回三个列表
1、当参数1 序列中的fd满足“可读”条件时,则获取发生变化的fd并添加到fd_r_list中
2、当参数2 序列中含有fd时,则将该序列中所有的fd添加到 fd_w_list中
3、当参数3 序列中的fd发生错误时,则将该发生错误的fd添加到 fd_e_list中
4、当超时时间为空,则select会一直阻塞,直到监听的句柄发生变化
当超时时间 = n(正整数)时,那么如果监听的句柄均无任何变化,则select会阻塞n秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。

实例:利用select实现一个可并发的服务端

#!/usr/bin/env python3
# -*- coding:utf-8 -*- import socket
import select sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk1.bind('127.0.0.1',9999) sk1.listen(5)
sk1.setblocking(0) inputs = [sk1,] while True:
readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 10)
for r in readable_list:
if sk1 == r:
print("Accept")
conn, address = r.accept()
conn.setblocking(0)
inputs.append(conn)
else:
received = r.recv(1024)
if received:
print("Received data:",received)
else:
inputs.remove(r)
sk1.close()
#!/usr/bin/env python3
# -*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',9999)
sk = socket.socket()
sk.connect(ip_port) while True:
inp = input('Please input:')
sk.sendall(inp)
sk.close()

在服务端我们可以看到,我们需要不停的调用select, 这就意味着:

  1  当文件描述符过多时,文件描述符在用户空间与内核空间进行copy会很费时

  2  当文件描述符过多时,内核对文件描述符的遍历也很浪费时间

  3  select最大仅仅支持1024个文件描述符

poll与select相差不大,本文不作介绍

epoll方法:

epoll很好的改进了select:

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

  2  epoll会在epoll_ctl时把指定的fd遍历一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd

  3  epoll对文件描述符没有额外限制

select.epoll(sizehint=-1, flags=0) 创建epoll对象。

epoll.close()
Close the control file descriptor of the epoll object.关闭epoll对象的文件描述符 epoll.closed
True if the epoll object is closed.检测epoll对象是否关闭 epoll.fileno()
Return the file descriptor number of the control fd.返回epoll对象的文件描述符 epoll.fromfd(fd)
Create an epoll object from a given file descriptor.根据指定的fd创建epoll对象 epoll.register(fd[, eventmask])
Register a fd descriptor with the epoll object.向epoll对象中注册fd和对应的事件 epoll.modify(fd, eventmask)
Modify a registered file descriptor.修改fd的事件 epoll.unregister(fd)
Remove a registered file descriptor from the epoll object.取消注册 epoll.poll(timeout=-1, maxevents=-1)
Wait for events. timeout in seconds (float)阻塞,直到注册的fd事件发生,会返回一个dict,格式为:{(fd1,event1),(fd2,event2),……(fdn,eventn)}
EPOLLIN    Available for read 可读   状态符为1
EPOLLOUT Available for write 可写 状态符为4
EPOLLPRI Urgent data for read
EPOLLERR Error condition happened on the assoc. fd 发生错误 状态符为8
EPOLLHUP Hang up happened on the assoc. fd 挂起状态
EPOLLET Set Edge Trigger behavior, the default is Level Trigger behavior 默认为水平触发,设置该事件后则边缘触发
EPOLLONESHOT Set one-shot behavior. After one event is pulled out, the fd is internally disabled
EPOLLRDNORM Equivalent to EPOLLIN
EPOLLRDBAND Priority data band can be read.
EPOLLWRNORM Equivalent to EPOLLOUT
EPOLLWRBAND Priority data may be written.
EPOLLMSG Ignored. 复制代码

水平触发和边缘触发:

Level_triggered(水平触发,有时也称条件触发):当被监控的文件描述符上有可读写事件发生时,epoll.poll()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll.poll()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!! 优点很明显:稳定可靠

Edge_triggered(边缘触发,有时也称状态触发):当被监控的文件描述符上有可读写事件发生时,epoll.poll()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll.poll()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!缺点:某些条件下不可靠。

#!/usr/bin/env python3
# -*- coding:utf-8 -*- import socket
import select sk = socket.socket()
sk.bind(('127.0.0.1',8888))
sk.listen(5) epoll_obj = select.epoll()
epoll_obj.register(sk, select.EPOLLN) connections = {}
while True:
events = epoll_obj.poll()
for fd, event in events:
print(fd, event)
if fd == sk.fileno():
conn, addr = sk.accept()
connections[conn.fileno()] = conn
epoll_obj.register(conn, select.EPOLLIN)
msg = conn.recv(1024)
conn.sendall("OK".encode())
else:
try:
fd_obj = connections[fd]
msg = fd_obj.recv(200)
fd_obj.sendall("ok".encode())
except BrokenPipeError:
epoll_obj.unregister(fd)
connections[fd].close()
del connections[fd]
sk.close()
epoll_obj.close()
#!/usr/bin/env python3
# -*- coding:utf-8 -*- import socket flag = 1
sk = socket.socket()
sk.connect(('127.0.0.1',8888)) while flag:
input_msg = input('input>>>')
if input_msg == '':
break
sk.sendall(input_msg.encode())
msg = sk.recv(1024)
print(msg.decode())
sk.close()
 摘自:http://www.cnblogs.com/MnCu8261/p/6403340.html
 
 

Python实战之IO多路复用select的详细简单练习的更多相关文章

  1. Python实战之IO多路复用select实例

    Select方法: 句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)   参数: 可接受四个参数(前三个必须) 返回值 ...

  2. python学习之-- IO多路复用 select模块

    python I/O多路复用包括3个模块,上一篇已经说过概念,这里我使用的是select模块实现一个ftp并发 服务器端核心代码: import socket,select import queue, ...

  3. python网络编程——IO多路复用select/poll/epoll的使用

    转载博客: http://www.haiyun.me/archives/1056.html http://www.cnblogs.com/coser/archive/2012/01/06/231521 ...

  4. python网络编程——IO多路复用之select

    1 IO多路复用的概念 原生socket客户端在与服务端建立连接时,即服务端调用accept方法时是阻塞的,同时服务端和客户端在收发数据(调用recv.send.sendall)时也是阻塞的.原生so ...

  5. socket_server源码剖析、python作用域、IO多路复用

    本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...

  6. 第五十五节,IO多路复用select模块加socket模块,伪多线并发

    IO多路复用select模块加socket模块,伪多线并发,并不是真正的多线程并发,实际通过循环等待还是一个一个处理的 IO多路复用,lo就是文件或数据的输入输出,IO多路复用就是可以多用户操作 IO ...

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

    python网络编程——IO多路复用之epoll 1.内核EPOLL模型讲解     此部分参考http://blog.csdn.net/mango_song/article/details/4264 ...

  8. Linux IO多路复用 select

    Linux IO多路复用 select 之前曾经写过简单的服务器,服务器是用多线程阻塞,客户端每一帧是用非阻塞实现的 后来发现select可以用来多路IO复用,就是说可以把服务器这么多线程放在一个线程 ...

  9. io多路复用-select()

    参照<Unix网络编程>相关章节内容,实现了一个简单的单线程IO多路复用服务器与客户端. 普通迭代服务器,由于执行recvfrom则会发生阻塞,直到客户端发送数据并正确接收后才能够返回,一 ...

随机推荐

  1. Springmvc_validation 效验器

    springmvc-validation效验器的使用介绍 对于任何一个应用来说,都会做数据的有效性效验,但是只在前端做并不是很安全,考虑到安全性這个时候会要求我们在服务端也对数据进行有效验证,spri ...

  2. 垂直居中小记 line-height table vertical-align:middle

    垂直居中分两种情况:1.父元素高度确定的单行文本        2.以及父元素高度确定的多行文本. 1.垂直居中-父元素高度确定的单行文本的竖直居中的方法是通过设置父元素的 height 和 line ...

  3. [转载]Web前端和后端之区分,以及面临的挑战【转】

    原文地址:Web前端和后端之区分,以及面临的挑战[转]作者:joyostyle 在我们实际的开发过程中,我们当前这样定位前端.后端开发人员. 1)前端开发人员:精通JS,能熟练应用JQuery,懂CS ...

  4. 避免subList/subString陷阱

    避免subList/subString陷阱 java.util.List 接口提供了一个实例方法 List<E> subList(int fromIndex, int toIndex), ...

  5. 团队作业10--Beta阶段项目复审

    小组的名字和链接 优点 缺点 最终排名 油炸咸鱼 http://www.cnblogs.com/24app/ 基本功能实现,能够完成预期达到的大部分功能,并能够修复所有自己提出的bug,界面也还行,博 ...

  6. 团队作业6--展示博客(Alpha版本)

    1.团队成员简介和个人博客地址 团队源码仓库地址:https://git.coding.net/tuoxie/dianziwendangchachong.git吕志哲 201421123021 个人博 ...

  7. 201521123081《Java程序设计》 第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 参考资料:XMIND 1.2 选做:收集你认为有用的代码片段 2. 书面作业 本次作业题集 集合 Q1. Li ...

  8. 201521123038 《Java程序设计》 第四周学习总结

    201521123038 <Java程序设计> 第四周学习总结 1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 1.通过 ins ...

  9. 201521123111《Java程序设计》第13周学习总结

    本次作业参考文件 正则表达式参考资料 1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.bai ...

  10. 201521123074 《Java程序设计》第10周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 Q1.finally 题目4-2 1.1 截图你的提交结果( ...