PYTHON 实现IO多路复用的方法

方法1: select(rlist,wlist,xlist)方法(用的多)

特点
优点:跨平台性好(win linux unix)
缺点:效率一般(每次监控IO都需要将应用层关注的IO映射给内核处理,应用层处理就绪IO时需要再次轮寻),最多监控1024个IO

流程

  1. 将关注的IO放入对应的监控类别列表
  2. 通过select函数进行监控
  3. 遍历select返回值列表,确定就绪IO事件
  4. 处理发生的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		

流程

  1. 创建连接套接字s
  2. 在系统中register套接字s
  3. 创建查找字典,并在系统中维护轮询监控
  4. 循环监控IO发生
  5. 处理发生的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()(用的多)

特点
优点:

  1. 监控IO没有限制,效率高(直接将关注的IO放到内核空间进行监控,不必每次都从应用层映射给内容,且内核会直接提供就绪的IO给应用层处理,应用层不必轮寻)
  2. 提供了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')
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值