io多路复用的原理和实现_python--io多路复用之select实现

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

2、I/O多路复用避免阻塞在io上,原本为多进程或多线程来接收多个连接的消息变为单进程或单线程保存多个socket的状态后轮询处理。

cf4ce0ed9301d944c31bfc505c1e6c60.gif

select

select是通过系统调用来监视一组由多个文件描述符组成的数组,通过调用select()返回结果,数组中就绪的文件描述符会被内核标记出来,然后进程就可以获得这些文件描述符,然后进行相应的读写操作

select的实际执行过程如下:

1、select需要提供要监控的数组,然后由用户态拷贝到内核态。

2、内核态线性循环监控数组,每次都需要遍历整个数组。

3、内核发现文件描述符状态符合操作结果,将其返回。

所以对于我们监控的socket都要设置为非阻塞的,只有这样才能保证不会被阻塞。

优点

基本各个平台都支持

缺点

1、每次调用select,都需要把fd集合由用户态拷贝到内核态,在fd多的时候开销会很大

2、单个进程能够监控的fd数量存在最大限制,因为其使用的数据结构是数组。

3、每次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秒,之后返回三个空列表。 如果由变化,则直接执行并返回。

7fcbd0670a2dfa1ae2e0cc3d81ca721f.gif

一、基于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 select22 23 while True:24 #[sk1, sk2, sk3],select内部启动监听sk1, sk2, sk3三个对象,一旦某个句柄发生变化25 #如果有人用sk126 #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)
0d6076046143bd5e7f89b2831b3cb20a.png

二、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)

三、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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值