python之路10:IO多路复用
- IO模型
- select模块
- selectors模块
IO模型
网络IO的本质是socket的读取,socket在linux系统中被抽象为流,IO可以理解为对流的操作.对于一次IO访问,数据会先被拷到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间,所以说当一个read操作发生时,它会经理两个阶段:
第一阶段:等待数据准备;第二阶段:将数据从内核拷贝到进程中
对socket流而言:第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。第二步:把数据从内核缓冲区复制到应用进程缓冲区。
IO的模型大致有如下几种:
异步IO(asynchronous IO)
同步IO(synchronous IO)
- 阻塞IO(bloking IO)
- 非阻塞IO(non-blocking IO)
- 多路复用IO(multiplexing IO)
- 信号驱动式IO(signal-driven IO)
同步阻塞 IO(blocking IO)
同步非阻塞 IO(nonblocking IO)
IO 多路复用( IO multiplexing)
信号驱动式 IO(signal-driven IO)
异步非阻塞 IO(asynchronous IO)
五种模型总结
select模块
Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。当用户进程调用了select,那么整个进程会被block
,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
select(rlist, wlist, xlist, timeout
=
None
)
select 函数监视的文件描述符分3类,分别是readfds、writefds和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。
应用:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from socket import * 6 import select 7 8 server = socket(AF_INET, SOCK_STREAM) # ipv4, tcp 9 server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 端口重用 10 server.bind(('127.0.0.1', 8090)) 11 server.listen(10) 12 server.setblocking(False) # 设置socket的套接字为非阻塞 13 read_l = [server, ] # 因为不只就那么一个列表要检测,所以不要在参数里面定死了 14 while True: 15 r_l, w_l, x_l = select.select(read_l, [], []) 16 print(r_l) # 检测到有数据 17 for ready_obj in r_l: 18 if ready_obj == server: 19 conn, addr = ready_obj.accept() # accept要经历两个阶段,但是程序如果走到这一步,那肯定是数据准备好了 20 # print(addr) 21 read_l.append(conn) 22 else: 23 try: 24 data = ready_obj.recv(1024) # 此时的ready_obj等于conn 25 if not data: 26 read_l.remove(ready_obj) 27 continue 28 ready_obj.send(data.upper()) 29 except ConnectionResetError: 30 read_l.remove(ready_obj)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from socket import * 6 client = socket(AF_INET, SOCK_STREAM) # ipv4, tcp 7 client.connect(('127.0.0.1', 8091)) 8 while True: 9 msg = input('>>: ') 10 if not msg: 11 continue 12 client.send(msg.encode('utf-8')) 13 data = client.recv(1024) 14 print(data.decode('utf-8'))
selectors模块
Select\Poll\Epoll这三种IO多路复用模型在不同的平台有着不同的支持,相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。而epoll在windows下就不支持,好在我们有selectors模块,帮我们默认选择当前平台下最合适的。该模块允许基于所选模块原语的高水平和高效的IO多路复用。鼓励用户使用此模块,除非他们希望对所使用的操作系统级原语进行精确控制。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 import selectors 6 from socket import * 7 8 sel = selectors.DefaultSelector() # 选择最佳实施 9 10 11 def accept(server_fileobj, mask): 12 conn, addr = server_fileobj.accept() 13 print('accepted', conn, 'from', addr) 14 conn.setblocking(False) 15 sel.register(conn, selectors.EVENT_READ, read) 16 17 18 def read(conn, mask): 19 data = conn.recv(1024) 20 if data: 21 print('echoing', repr(data), 'to', conn) 22 conn.send(data) 23 else: 24 print('closing', conn) 25 sel.unregister(conn) 26 conn.close() 27 28 29 server_fileobj = socket(AF_INET, SOCK_STREAM) 30 server_fileobj.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) 31 server_fileobj.bind(('localhost', 2130)) 32 server_fileobj.listen(200) 33 server_fileobj.setblocking(False) # 设置socket的接口为非阻塞 34 # 相当于往select的读列表里append了一个文件句柄server_fileobj,并且绑定了一个回调函数accept 35 sel.register(server_fileobj, selectors.EVENT_READ, accept) 36 37 while True: 38 events = sel.select() # 检测所有的fileobj,是否有完成wait data的 39 for sel_obj, mask in events: 40 callback = sel_obj.data # callback=accpet 41 callback(sel_obj.fileobj, mask) # accpet(server_fileobj,1)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from socket import * 6 client = socket(AF_INET, SOCK_STREAM) # ipv4, tcp 7 client.connect(('localhost', 2130)) 8 while True: 9 msg = input('>>: ') 10 if not msg: 11 continue 12 client.send(msg.encode('utf-8')) 13 data = client.recv(1024) 14 print(data.decode('utf-8'))
参考:
http://www.cnblogs.com/alex3714
http://www.cnblogs.com/wupeiqi
internet&python books
PS:如侵权,联我删。