python自动化开发学习-10 I/O多路复用
一. 简介
socketserver在内部是由I/O多路复用,多线程和多进程,实现了并发通信。IO多路复用的系统消耗很小。
IO多路复用底层就是监听socket对象内部是否有变化,是否在收发消息,Python中select模块提供了select poll epoll 三种方式来实现IO多路复用,支持不同的操作系统。
windows : 提供select
Mac : 提供select
Linux : 提供select, poll, epoll
(1)select简介:
select 默认会将每一个变化的socket加入一个他自己维护的列表中,但是并不会明确是哪一个socket,它内部其实是通过一个for循环遍历整个列表,当列表中的socket很多的时候,for循环就会浪费资源。select默认可以监听1024个链接,它是由linux在同文件中定义的,可修改。select不是安全的线程,如果你把一个socket加入到select中,突然另外一个线程关闭了这个socket,那么接下来select的行为是随机的。
(2)poll简介:
poll修复了select的一些问题,例如去掉了1024个链接限制,想要监听多少都可以,poll不在修改传入的数组,但是poll依然不是线程安全的。
(3)epoll简介:
epoll不仅会告诉你socket组里面的数据,还会告诉你具体哪个socket有数据,无需自己寻找。而且epoll终于是线程安全的了。然而epoll局限于
Linux操作系统。
二 . 应用
select 方法
句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)<br> 参数: 可接受四个参数(前三个必须) 返回值:三个列表 select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。 1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中 2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中 3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中 4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化 当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
(1)监听客户端连接
import socket import select sk = socket.socket() sk.bind(("127.0.0.1", 9999,)) sk.listen(5) while True: rlist, w, e = select.select([sk, ], [], [], 5) # 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk] # 监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端] print(rlist) for r in rlist: print(r) conn, address = r.accept() conn.sendall(bytes("hello", encoding="utf-8"))
import socket sk = socket.socket() sk.connect(("127.0.0.1", 9999,)) data = sk.recv(1024) print(data) while True: input(">>") sk.close()
(2)监听客户端连接及接受客户端发送的数据
import socket import select sk = socket.socket() sk.bind(("127.0.0.1", 9999,)) sk.listen(5) inputs = [sk,] while True: rlist, w, e = select.select(inputs, [], [], 1) # 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk] # 监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端] print(len(inputs), len(rlist)) for r in rlist: print(r) if r == sk: # 有新的客户端连接进来 conn, address = r.accept() inputs.append(conn) conn.sendall(bytes("hello", encoding="utf-8")) else: # 有客户端给我发送数据 recv_data = r.recv(1024) print(recv_data)
import socket sk = socket.socket() sk.connect(("127.0.0.1", 9999,)) data = sk.recv(1024) print(data) while True: inp = input(">>") sk.sendall(bytes(inp, encoding='utf-8')) sk.close()
(3)读写分离
import socket import select sk = socket.socket() sk.bind(("127.0.0.1", 9999,)) sk.listen(5) inputs = [sk,] outputs = [] while True: rlist, wlist, e = select.select(inputs, outputs, [], 1) # 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk] # 监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端] print(len(inputs), len(rlist)) for r in rlist: print(r) if r == sk: # 有新的客户端连接进来 conn, address = r.accept() inputs.append(conn) conn.sendall(bytes("hello", encoding="utf-8")) else: # 有客户端给我发送数据 try: recv_data = r.recv(1024) if not recv_data: # 某些操作系统,在socket断开的时候,会发送一个空给server,所以要判断空的情况 raise Exception("断开连接") else: outputs.append(r) except Exception as e: inputs.remove(r) for w in wlist: w.sendall(bytes("response", encoding="utf-8")) outputs.remove(w)
import socket sk = socket.socket() sk.connect(("127.0.0.1", 9999,)) data = sk.recv(1024) print(data) while True: inp = input(">>") sk.sendall(bytes(inp, encoding='utf-8')) print(sk.recv(1024)) sk.close()
(4)读写分离之服务端根据客户端的输入返回对应的信息
import socket import select sk = socket.socket() sk.bind(("127.0.0.1", 9999,)) sk.listen(5) inputs = [sk, ] # outputs = [] messages = {} # 用来存放客户端发送过来的消息,存放格式 连接客户端的对象: 消息 while True: rlist, wlist, elist = select.select(inputs, outputs, [sk, ], 1) # rlist 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk] # wlist 监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端] # elist 监听sk(服务器端)对象是否发生错误 print(len(inputs), len(rlist)) for r in rlist: print(r) if r == sk: # 有新的客户端连接进来 conn, address = r.accept() inputs.append(conn) messages[conn] = [] # 在存储消息的字典中创建一个以客户端连接对象为key 空列表为value的键值对 conn.sendall(bytes("hello", encoding="utf-8")) else: # 有客户端给我发送数据 try: recv_data = r.recv(1024) if not recv_data: # 某些操作系统,在socket断开的时候,会发送一个空给server,所以要判断空的情况 raise Exception("断开连接") else: outputs.append(r) messages[r].append(recv_data) except Exception as e: inputs.remove(r) del messages[r] for w in wlist: print(messages[w]) msg = messages[w].pop() resp = msg + bytes("response", encoding="utf-8") w.sendall(resp) outputs.remove(w)
import socket sk = socket.socket() sk.connect(("127.0.0.1", 9999,)) data = sk.recv(1024) print(data) while True: inp = input(">>") sk.sendall(bytes(inp, encoding='utf-8')) print(sk.recv(1024)) sk.close()