python/网络:标准库模块select(IO多路复用实现)
1、多路复用的实现
https://docs.python.org/zh-cn/3/library/select.html#module-select
用 select模块,select模块在Windows下只有select方法。
linux下有select、poll方法和epoll,epoll是select下的实现的一个类,它有自己的一些方法。
select ---> windows linux unix
poll ---> linux unix
epoll ---> linux unix
【1】select实现IO多路复用
from select import *
rs, ws, xs = select(rlist, wlist, xlist[, timeout])
参数 : rlist ,列表, 存放我们需要等待处理的IO
wlist, 列表, 存放我们想主动处理的IO
xlist, 列表, 存放出错希望去处理的IO
timeout, 超时检测
功能 : 监控IO事件,阻塞等待监控的IO时间发生
返回值: rs ,列表, rlist中准备就绪的IO
ws, 列表, wlist中准备就绪的IO
xs, 列表, xlist中准备就绪的IO
【注意】
【1】在处理IO时不要形成死循环,让一个客户端单独占有服务端
【2】IO多路复兴形成一种可以同时处理多个IO的效果,效率较高
from socket import * from select import select s = socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('0.0.0.0',8888)) s.listen(5) rlist = [s] wlist = [] xlist = [s] while True: print("等待IO发生") rs,ws,xs = select(rlist,wlist,xlist) for r in rs: if r is s: connfd,addr = r.accept() print("Connect from",addr) rlist.append(connfd) #表示客户端连接套接字准备就绪\'///\\\\\\\\\\\\\\\' else: data = r.recv(1024) if not data: #从关注列表移除 rlist.remove(r) r.close() else: print("Receive:",data.decode()) #讲客户端套接字放入wlist wlist.append(r) for w in ws: w.send("这是一条回复消息".encode()) wlist.remove(w) for x in xs: if x is s: s.close() 00..
from socket import * #创建套接字 sockfd = socket() #发起连接 sockfd.connect(('127.0.0.1',8888)) while True: #消息收发 msg = input("Msg>>") if not msg: break sockfd.sendall(msg.encode()) data = sockfd.recv(1024) print(data.decode()) sockfd.close()
【2】poll方法实现IO多路复用
【1】 创建poll对象
p = select.poll()
【2】 注册关注的IO
p.register(s,POLLIN | POLLERR)
p.unregister(s) # 取消IO关注
【事件类别】
POLLIN POLLOUT POLLERR POLLHUP POLLPRI
rlist wlist xlist 断开 紧急处理
【3】 监控IO
events = p.poll()
功能: 监控关注的IO事件
返回值: 返回发生的IO事件, events 是一个列表 [(fileno,evnet),(),()....] ,每个就绪IO对应列表中一个元组:(描述符,就绪事件)
IO地图 : {s.fileno():s}
【4】处理IO事件
from socket import * from select import * s = socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('0.0.0.0',8888)) s.listen(5) #创建poll对象 p = poll() #创建地图 fdmap = {s.fileno():s} #添加关注 p.register(s,POLLIN | POLLERR) while True: #进行IO监控 #[(fileno,evnet),...] events = p.poll() for fd,event in events: if fd == s.fileno(): #从地图中找到fd对应的对象 c,addr = fdmap[fd].accept() print("Connect from",addr) #注册新的IO 维护地图 p.register(c,POLLIN) fdmap[c.fileno()] = c else: data = fdmap[fd].recv(1024) if not data: p.unregister(fd) #从关注移除 fdmap[fd].close() del fdmap[fd] #从地图删除 else: print(data.decode()) fdmap[fd].send('收到了'.encode())
from socket import * #创建套接字 sockfd = socket() #发起连接 sockfd.connect(('127.0.0.1',8888)) while True: #消息收发 msg = input("Msg>>") if not msg: break sockfd.sendall(msg.encode()) data = sockfd.recv(1024) print(data.decode()) sockfd.close()
【3】epoll方法实现IO多路复用
代码与poll基本一致
① 将生产的对象改为 p = epoll()
② 将关注事件类别名称改为epoll的关注事件类别
from socket import * from select import * s = socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('0.0.0.0',8888)) s.listen(5) #创建epoll对象 p = epoll() #创建地图 fdmap = {s.fileno():s} #添加关注 p.register(s,EPOLLIN | EPOLLERR) while True: #进行IO监控 #[(fileno,evnet),...] events = p.poll() for fd,event in events: if fd == s.fileno(): #从地图中找到fd对应的对象 c,addr = fdmap[fd].accept() print("Connect from",addr) #注册新的IO 维护地图 p.register(c,EPOLLIN) fdmap[c.fileno()] = c else: data = fdmap[fd].recv(1024) if not data: p.unregister(fd) #从关注移除 fdmap[fd].close() del fdmap[fd] #从地图删除 else: print(data.decode()) fdmap[fd].send('收到了'.encode())
from socket import * #创建套接字 sockfd = socket() #发起连接 sockfd.connect(('127.0.0.1',8888)) while True: #消息收发 msg = input("Msg>>") if not msg: break sockfd.sendall(msg.encode()) data = sockfd.recv(1024) print(data.decode()) sockfd.close()
2、三者区别
【1】epoll 效率要高于select 和 poll
【2】epoll 的触发机制更多--> EPOLLET(边缘触发)