一、I/O多路复用指:经过一种机制,能够监视多个描述符,一旦某个描述符就绪(通常是读就绪或者写就绪),可以通知程序进行相应的读写操做。
二、I/O多路复用避免阻塞在io上,本来为多进程或多线程来接收多个链接的消息变为单进程或单线程保存多个socket的状态后轮询处理。
select
select是经过系统调用来监视一组由多个文件描述符组成的数组,经过调用select()返回结果,数组中就绪的文件描述符会被内核标记出来,而后进程就能够得到这些文件描述符,而后进行相应的读写操做python
select的实际执行过程以下:linux
一、select须要提供要监控的数组,而后由用户态拷贝到内核态。windows
二、内核态线性循环监控数组,每次都须要遍历整个数组。数组
三、内核发现文件描述符状态符合操做结果,将其返回。服务器
因此对于咱们监控的socket都要设置为非阻塞的,只有这样才能保证不会被阻塞。数据结构
优势
基本各个平台都支持多线程
缺点
一、每次调用select,都须要把fd集合由用户态拷贝到内核态,在fd多的时候开销会很大app
二、单个进程可以监控的fd数量存在最大限制,由于其使用的数据结构是数组。socket
三、每次select都是线性遍历整个数组,当fd很大的时候,遍历的开销也很大函数
python使用select
语法:r_list, w_list, e_list = select.select( rlist, wlist, errlist [,timeout] )
说明详解:
rlist,wlist和errlist均是waitable object; 都是文件描述符,就是一个整数,或者一个拥有返回文件描述符的函数fileno()的对象。
rlist: 等待读就绪的文件描述符数组
wlist: 等待写就绪的文件描述符数组
errlist: 等待异常的数组
在linux下这三个列表能够是空列表,可是在windows上不行
当rlist数组中的文件描述符发生可读时(调用accept或者read函数),则获取文件描述符并添加到r数组中。
当wlist数组中的文件描述符发生可写时,则获取文件描述符添加到w数组中
当errlist数组中的文件描述符发生错误时,将会将文件描述符添加到e队列中
当超时时间没有设置时,若是监听的文件描述符没有任何变化,将会一直阻塞到发生变化为止
当超时时间设置为1时,若是监听的描述符没有变化,则select会阻塞1秒,以后返回三个空列表。 若是由变化,则直接执行并返回。
1、基于select实现的IO多路复用的基础实例:
io_server.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 IO多路复用服务器端 5 """ 6 import socket 7 8 sk1 = socket.socket() 9 sk1.bind(('127.0.0.1', 8001)) 10 sk1.listen(5) 11 12 sk2 = socket.socket() 13 sk2.bind(('127.0.0.1', 8002)) 14 sk2.listen(5) 15 16 sk3 = socket.socket() 17 sk3.bind(('127.0.0.1', 8003)) 18 sk3.listen(5) 19 20 inputs = [sk1, sk2, sk3] 21 import select 22 23 while True: 24 #[sk1, sk2, sk3],select内部启动监听sk1, sk2, sk3三个对象,一旦某个句柄发生变化 25 #若是有人用sk1 26 #r_list = [sk1, sk2, sk3] 27 r_list, w_list, e_list = select.select(inputs, [], [], 1) 28 print(r_list) 29 for sk in r_list: 30 #每个链接对象 31 conn, address = sk.accept() 32 conn.sendall(bytes('Hello', encoding='utf-8')) 33 conn.close()
io_client.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 客户端1,请求8001端口 5 """ 6 import socket 7 8 ck = socket.socket() 9 ck.connect(('127.0.0.1', 8001)) 10 11 content = str(ck.recv(1024), encoding='utf-8') 12 print(content)
2、IO多路复用服务器端升级改造后的代码实现
io_server2.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 IO多路复用服务器端升级改造 5 """ 6 7 import socket 8 import select 9 10 sk = socket.socket() 11 12 sk.bind(('127.0.0.1', 8001)) 13 sk.listen() 14 15 inputs = [sk,] 16 while True: 17 r_list, w_list, e_list = select.select(inputs, [], [], 1) 18 print('正在监听的socket对象:%d' % len(inputs)) 19 for sk_or_conn in r_list: 20 #每个链接对象 21 if sk_or_conn == sk: 22 #表示有新用户来链接 23 conn, address = sk.accept() 24 inputs.append(conn) 25 else: 26 #有老用户发消息了 27 try: 28 data_bytes = sk_or_conn.recv(1024) 29 except Exception as ex: 30 #若是有用户终断链接,则移除句柄 31 inputs.remove(sk_or_conn) 32 else: 33 #用户正常发送信息 34 data_str = str(data_bytes, encoding='utf-8') 35 sk_or_conn.sendall(bytes(data_str + '好', encoding='utf-8')) 36 37 for sk in e_list: 38 inputs.remove(sk)
3、IO多路复用服务器端升级改造,读、写分离
io_server3.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 IO多路复用服务器端升级改造,读、写分离 5 """ 6 7 import socket 8 import select 9 10 sk = socket.socket() 11 sk.bind(('127.0.0.1', 8001)) 12 sk.listen() 13 14 inputs = [sk,] 15 outputs = [] 16 message_dict = {} 17 18 while True: 19 20 r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1) 21 22 print('正在监听的socket对象:%d' % len(inputs)) 23 for sk_or_conn in r_list: 24 #每个链接对象 25 if sk_or_conn == sk: 26 #表示有新用户来链接 27 conn, address = sk.accept() 28 inputs.append(conn) 29 #将链接的用户添加到字典中 30 message_dict[conn] = [] 31 else: 32 #有老用户发消息了 33 try: 34 data_bytes = sk_or_conn.recv(1024) 35 except Exception as ex: 36 #若是有用户终断链接,则移除句柄 37 inputs.remove(sk_or_conn) 38 else: 39 # 用户正常发送信息 40 data_str = str(data_bytes, encoding='utf-8') 41 message_dict[sk_or_conn].append(data_str) #将用户发送过来的信息存在在字典中 42 # sk_or_conn.sendall(bytes(data_str + '好', encoding='utf-8')) 43 outputs.append(sk_or_conn) 44 45 #写操做 46 for sk_out in w_list: 47 recv_data = message_dict[sk_out][0] #从字典中获取信息数据 48 del message_dict[sk_out][0] #获取数据后,清空字典,等待存储下次的数据 49 sk_out.sendall(bytes(recv_data + '好', encoding='utf-8')) 50 outputs.remove(sk_out) 51 52 for sk in e_list: 53 inputs.remove(sk)