Python select及selectors模块概念用法详解
1.select模块
针对select,要先理解其他几个概念:
文件描述符:
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
内核空间:
Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。
内核空间和用户空间一般通过系统调用进行通信。
select就是针对许多文件描述符(简称fd)进行监控,它有三个参数:
- rlist--waituntilreadyforreading
- wlist--waituntilreadyforwriting
- xlist--waitforan"exceptionalcondition"
第一个参数监控进来的数据的fd列表,select监控这个列表,等待这些fd发送过来数据,一旦数据发送过来了(可以读取了),就返回一个可读的fd列表
第二个参数监控出去的数据的fd列表,select监控这个列表,等待这些fd发送出去数据,一旦fd准备好发送了(可以写入了),就返回一个可写的fd列表
第三个参数监控fd列表,返回出异常的fd列表
服务端:
importselect
importsocket
importsys
importqueue
#生成socket对象
server=socket.socket()
#设置非阻塞模式
server.setblocking(False)
#绑定地址,设置监听
server.bind(('localhost',9999))
server.listen(5)
#将自己也放进待监测列表里
inputs=[server,]
outputs=[]
message_queues={}
whileTrue:
'''
关于socket可读可写的判断,可以参考博客:https://blog.csdn.net/majianfei1023/article/details/45788591
'''
rlist,wlist,elist=select.select(inputs,outputs,inputs)#如果没有任何fd就绪,那程序就会一直阻塞在这里
forrinrlist:#遍历已经可以准备读取数据的fd
ifrisserver:#如果这个fd是server,即server有数据待接收读取,说明有新的客户端连接过来了
conn,client_addr=r.accept()
print("newconnectionfrom",client_addr)
conn.setblocking(False)
inputs.append(conn)#将这个新的客户端连接添加到检测的列表中
message_queues[conn]=queue.Queue()#用队列存储客户端发送来的数据,等待服务器统一返回数据
else:#这个可读的r不是服务器,那就是某个客户端。就是说客户端发送数据过来了,这些数据处于待读取状态
try:#异常处理,这是为了防止客户端异常断开报错(比如手动关掉客户端黑窗口,服务器也会跟着报错退出)
data=r.recv(1024)
ifdata:#根据判断data是否为空,判断客户端是否断开
print("收到来自[%s]的数据:"%r.getpeername()[0],data)
message_queues[r].put(data)#收到的数据先放到queue里,一会返回给客户端
ifrnotinoutputs:
outputs.append(r)#放进可写的fd列表中,表明这些fd已经准备好去发送数据了。
else:#如果数据为空,表明客户端断开了
print('客户端断开了')
ifrinoutputs:
outputs.remove(r)#清理已断开的连接
inputs.remove(r)#清理已断开的连接
delmessage_queues[r]#清理已断开的连接
exceptConnectionResetError:#如果报错,说明客户端断开了
print("客户端异常断开了",r)
ifrinoutputs:
outputs.remove(r)#清理已断开的连接
inputs.remove(r)#清理已断开的连接
delmessage_queues[r]#清理已断开的连接
forwinwlist:#遍历可写的fd列表,即准备好发送数据的那些fd
#判断队列是否为空
try:
next_msg=message_queues[w].get_nowait()
exceptqueue.Empty:
#print("client[%s]"%w.getpeername()[0],"queueisempty..")
outputs.remove(w)
#队列不为空,就把队列中的数据改成大写,原样发回去
else:
#print("sendingmsgto[%s]"%w.getpeername()[0],next_msg)
w.send(next_msg.upper())
foreinelist:#处理报错的fd
e.close()
print("Erroroccuredin",e.getpeername())
inputs.remove(e)
ifeinoutputs:
outputs.remove(e)
delmessage_queues[e]
客户端:
importsocket
importsys
sock=socket.socket()
sock.connect(('localhost',9999))
whileTrue:
c=input('>>>:').strip()
sock.send(c.encode())
data=sock.recv(1024)
print(data.decode())
sock.close()
2.selectors模块
官方文档:https://docs.python.org/3/library/selectors.html
服务端:
importselectors
importsocket
#根据平台自动选择最佳的IO多路机制,比如linux就会选择epoll,windows会选择select
sel=selectors.DefaultSelector()
defaccept(sock,mask):
#建立客户端连接
conn,addr=sock.accept()
print('accepted',conn,'from',addr)
#设置非阻塞模式
conn.setblocking(False)
#再次注册一个连接,将其加入监测列表中,
sel.register(conn,selectors.EVENT_READ,read)
defread(conn,mask):
try:#抛出客户端强制关闭的异常(如手动关闭客户端黑窗口)
data=conn.recv(1000)#Shouldbeready
ifdata:
print('echoing',repr(data),'to',conn)
conn.send(data)#Hopeitwon'tblock
else:
print('Clientclosed.',conn)
#将conn从监测列表删除
sel.unregister(conn)
conn.close()
exceptConnectionResetError:
print('Clientforciblyclosed.',conn)
#将conn从监测列表删除
sel.unregister(conn)
conn.close()
#创建socket对象
sock=socket.socket()
#绑定端口,设置监听
sock.bind(('localhost',1234))
sock.listen(100)
#设置为非阻塞模式
sock.setblocking(False)
#注册一个文件对象,监测它的IO事件,data是和文件对象相关的数据(此处放置了一个accept函数的内存地址)
#register(fileobj,events,data=None)
sel.register(sock,selectors.EVENT_READ,accept)
whileTrue:
'''
sel.select()
看似是select方法,实际上会根据平台自动选择使用select还是epoll
它返回一个(key,events)元组,key是一个namedtuple类型的元组,可以使用key.name获取元组的数据
key的内容(fileobj,fd,events,data):
fileobj已经注册的文件对象
fd也就是第一个参数的那个文件对象的更底层的文件描述符
events等待的IO事件
data可选项。可以存一些和fileobj有关的数据,如sessioin的id
'''
events=sel.select()#监测有无活动对象,没有就阻塞在这里等待
forkey,maskinevents:#有活动对象了
callback=key.data#key.data是注册时传递的accept函数
callback(key.fileobj,mask)#key.fileobj就是传递的socket对象
客户端:
importsocket
tin=socket.socket()
tin.connect(('localhost',1234))
whileTrue:
inp=input('>>>>')
tin.send(inp.encode('utf8'))
data=tin.recv(1024)
print(data.decode('utf8'))
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。