python之路10:IO多路复用

  1. IO模型
  2. select模块
  3. selectors模块

IO模型

网络IO的本质是socket的读取,socket在linux系统中被抽象为流,IO可以理解为对流的操作.对于一次IO访问,数据会先被拷到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间,所以说当一个read操作发生时,它会经理两个阶段:

第一阶段:等待数据准备;第二阶段:将数据从内核拷贝到进程中

对socket流而言:第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。第二步:把数据从内核缓冲区复制到应用进程缓冲区。

IO的模型大致有如下几种:

异步IO(asynchronous IO)

同步IO(synchronous IO) 

  1. 阻塞IO(bloking IO)
  2. 非阻塞IO(non-blocking IO)
  3. 多路复用IO(multiplexing IO)
  4. 信号驱动式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)
服务端IO多路复用
 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'))
客户端IO多路复用

selectors模块

SelectPollEpoll这三种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:如侵权,联我删。

原文地址:https://www.cnblogs.com/BillyLV/p/11013410.html