方法1: select(rlist,wlist,xlist)方法(用的多)
特点
优点:跨平台性好(win linux unix)
缺点:效率一般(每次监控IO都需要将应用层关注的IO映射给内核处理,应用层处理就绪IO时需要再次轮寻),最多监控1024个IO
流程
- 将关注的IO放入对应的监控类别列表
- 通过select函数进行监控
- 遍历select返回值列表,确定就绪IO事件
- 处理发生的IO事件
代码
from socket import *
from select import select #python用select模块实现io多路复用
#创建监听套接字作为IO对象(读类型)
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #设置端口可以复用
s.bind(('0.0.0.0',8888))
s.listen(3)
#设置关注列表
rlist = [s] #监听套接字:s 只有读操作,等待客户端连接
wlist = []
xlist = []
#循环监听不同客户端发过来的请求,使s监听套接字一直处于r[0]就绪状态
while True:
#监控IO发生
#select函数语法:rs,ws,ws=select(rlist,wlist,xlist[,timeout])
rs,ws,xs = select(rlist,wlist,xlist) #监控新发过来的s监听套接字和c连接套接字,返回他们的就绪状态
for r in rs:
if r is s: #分情况讨论,如果是监听套接字,说明由客户端连接服务端,做以下事情(只能读)
#接收连接
c,addr = r.accept() #rs[0]表示准备就绪了的s监听套接字,c的本质是客户端给服务端发消息
print('Connect from',addr)
rlist.append(c) #将新生成的c套接字添加到读监听列表rlist中,让操作系统去监控
else: #分情况讨论,如果是连接套接字c,做下面的事情(既能写也能读)
data = r.recv(1024).decode()
if not data:
rlist.remove(r) #取消对它的关注
r.close()
continue #不能用break直接退出for循环,因为其他io还需继续执行下去
print(data)
# r.send(b'OK')
wlist.append(r) #将写操作(主动)放到wlist中监控,在下一次循环时立即返回
for w in ws:
w.send(b'OK')
wlist.remove(w) #从写监控中移除,如果不移除会一直主动写回发给客户端
方法2:poll()方法
特点
优点:监控IO数量没有限制
缺点:跨平台性一般(linux unix),效率一般(每次监控IO都需要将应用层关注的IO映射给内核处理,应用层需要再次轮寻找到就绪的IO)
API
p = select.poll()
#功能:创建poll对象
#返回值:poll对象
p.register(fd,event)
#功能:注册关注的IO事件
#参数:fd 要关注的IO
# event 要关注的IO类型
# 常用类型:POLLIN 读IO事件(rlist)
# POLLOUT 写IO事件(wlist)
# pollerr 异常IO (xlist)
# POLLHUP 断开连接
# 可使用位运算表示多个类型的IO
p.unregister(fd)
#功能:取消对IO的关注
#参数:IO对象或者IO对象的fileno
events = p.poll()
#功能:阻塞等待监控的IO事件发生
#返回值:返回发生的IO
# event格式 [(io_fileno,就绪io的类型值),()...]
# 每个元组为一个就绪IO,元组第一项是该IO的fileno,第二项是就绪的IO_event
流程
- 创建连接套接字s
- 在系统中register套接字s
- 创建查找字典,并在系统中维护轮询监控
- 循环监控IO发生
- 处理发生的IO
代码
from socket import *
from select import *
#创建套接字作为需要被关注的IO
s = socket()
s.setsocketopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind('0.0.0.0',8888))
s.listen(3)
#创建poll()对象
p = poll()
#创建查找字典:使用文件描述符fileno来查找对应的IO:{fileno:io_obj}
#字典内容需要与被关注的IO保持一致
fdmap = {s.fileno():s}
#关注s
p.register(s,POLLIN | POLLERR) #读IO或异常IO
#循环监控IO的发生(不断会有请求传进来)
while True:
events = p.poll() #阻塞等待IO发生,返回值为(io_fileno,io_events_类型值),不会直接返回就绪的io对象,需要自己使用字典维护
for fd,event in events:
#使用文件描述符判断返回的io是否是s(监听套接字),如果是则进行接下来的连接等操作
if fd == s.fileno():
c,addr = fdmap[fd].accept()
print('Connect from',addr)
#将c套接字提交io关注,同时将该io添加到监控字典中
p.register(c,POLLIN)
fdmap[c.fileno()] = c
#如果poll()出来的文件描述符是c连接套接字的话,则直接接收数据
else:
data = fdmap[fd].recv(1024).decode()
#客户端退出
if not data:
#取消对c连接套接字的监控
p.unregister(fd)
#关闭c连接套接字以释放资源
fdmap[fd].close()
#将字典时刻与IO保持一致,删掉该IO对象
del fdmap[fd]
continue
#如果客户端没有退出,打印出接收到的消息,并给客户端发送需要发送的消息
print(data)
fdmap[fd].send(b'OK')
方法3:epoll()(用的多)
特点
优点:
- 监控IO没有限制,效率高(直接将关注的IO放到内核空间进行监控,不必每次都从应用层映射给内容,且内核会直接提供就绪的IO给应用层处理,应用层不必轮寻)
- 提供了EPOLLET:边缘出发,可以忽略不想处理的IO
缺点:跨平台性差,只支持linux
使用方法
代码流程和poll()相同,只要将生成对象改为epoll(),io_event类型改为EPOLL类型就行
代码
from socket import *
from select import *
#创建套接字作为需要被关注的IO
s = socket()
s.setsocketopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind('0.0.0.0',8888))
s.listen(3)
#创建epoll()对象
ep = epoll()
#创建查找字典,{文件描述符fileno:io_obj}
#将s监听套接字维护进入系统的监控字典中
fdmap = {s.fileno():s}
#监控s
ep.register(s,EPOLLIN | EPOLLERR) #读IO或异常IO
#循环监控IO的发生(不断会有请求传进来)
while True:
events = ep.poll()
print(events)
for fd,event in events:
if fd == s.fileno():
c,addr = fdmap[fd.accept()
print('Connect from',addr)
ep.register(c,EPOLLIN)
fdmap[c.fileno()] = c
elif event & EPOLLIN:
data = fdmap[fd].recv(1024).decode()
if not data:
ep.unregister(fd)
fdmap.[fd].close()
def fdmap[fd]
continue
print(data)
fdmap[fd].send(b'OK')