Python select及selectors模块概念用法详解

作者:wztshine 时间:2022-11-30 20:06:24 

 1. select模块

针对select,要先理解其他几个概念:

文件描述符:

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

内核空间:

Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址 0x00000000到0xBFFFFFFF),供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。

内核空间和用户空间一般通过系统调用进行通信。

select就是针对许多文件描述符(简称fd)进行监控,它有三个参数:

  • rlist -- wait until ready for reading

  • wlist -- wait until ready for writing

  • xlist -- wait for an "exceptional condition"

第一个参数监控 进来的 数据的fd列表,select监控这个列表,等待这些fd发送过来数据,一旦数据发送过来了(可以读取了),就返回一个可读的fd列表

第二个参数监控 出去的 数据的fd列表,select监控这个列表,等待这些fd发送出去数据,一旦fd准备好发送了(可以写入了),就返回一个可写的fd列表

第三个参数监控fd列表,返回出异常的fd列表

服务端:


import select
import socket
import sys
import queue

# 生成socket对象
server = socket.socket()
# 设置非阻塞模式
server.setblocking(False)

# 绑定地址,设置监听
server.bind(('localhost',9999))
server.listen(5)

# 将自己也放进待监测列表里
inputs = [server, ]
outputs = []
message_queues = {}

while True:
 '''
 关于socket可读可写的判断,可以参考博客:https://blog.csdn.net/majianfei1023/article/details/45788591
 '''
 rlist, wlist, elist = select.select(inputs,outputs,inputs) #如果没有任何fd就绪,那程序就会一直阻塞在这里

for r in rlist: # 遍历已经可以准备读取数据的 fd
   if r is server: # 如果这个 fd 是server,即 server 有数据待接收读取,说明有新的客户端连接过来了
     conn, client_addr = r.accept()
     print("new connection from",client_addr)
     conn.setblocking(False)
     inputs.append(conn) # 将这个新的客户端连接添加到检测的列表中
     message_queues[conn] = queue.Queue() # 用队列存储客户端发送来的数据,等待服务器统一返回数据

else:     # 这个可读的 r 不是服务器,那就是某个客户端。就是说客户端发送数据过来了,这些数据处于待读取状态
     try:    # 异常处理,这是为了防止客户端异常断开报错(比如手动关掉客户端黑窗口,服务器也会跟着报错退出)
       data = r.recv(1024)
       if data:  # 根据判断data是否为空,判断客户端是否断开
         print("收到来自[%s]的数据:" % r.getpeername()[0], data)
         message_queues[r].put(data)  # 收到的数据先放到queue里,一会返回给客户端
         if r not in outputs:
           outputs.append(r)   # 放进可写的fd列表中,表明这些 fd 已经准备好去发送数据了。
       else:  # 如果数据为空,表明客户端断开了
         print('客户端断开了')
         if r in outputs:
           outputs.remove(r)  # 清理已断开的连接
         inputs.remove(r)     # 清理已断开的连接
         del message_queues[r]  # 清理已断开的连接
     except ConnectionResetError:   # 如果报错,说明客户端断开了
       print("客户端异常断开了", r)
       if r in outputs:
         outputs.remove(r)  # 清理已断开的连接
       inputs.remove(r)    # 清理已断开的连接
       del message_queues[r] # 清理已断开的连接

for w in wlist:    # 遍历可写的 fd 列表,即准备好发送数据的那些fd
   # 判断队列是否为空
   try :
     next_msg = message_queues[w].get_nowait()
   except queue.Empty:
     # print("client [%s]" % w.getpeername()[0], "queue is empty..")
     outputs.remove(w)
   # 队列不为空,就把队列中的数据改成大写,原样发回去
   else:
     # print("sending msg to [%s]"% w.getpeername()[0], next_msg)
     w.send(next_msg.upper())

for e in elist:  # 处理报错的 fd
   e.close()
   print("Error occured in ",e.getpeername())
   inputs.remove(e)
   if e in outputs:
     outputs.remove(e)
   del message_queues[e]

客户端:


import socket
import sys

sock = socket.socket()
sock.connect(('localhost',9999))
while True:
 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

服务端:


import selectors
import socket

# 根据平台自动选择最佳的IO多路机制,比如linux就会选择epoll,windows会选择select
sel = selectors.DefaultSelector()

def accept(sock, mask):
 # 建立客户端连接
 conn, addr = sock.accept()
 print('accepted', conn, 'from', addr)
 # 设置非阻塞模式
 conn.setblocking(False)
 # 再次注册一个连接,将其加入监测列表中,
 sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
 try:  # 抛出客户端强制关闭的异常(如手动关闭客户端黑窗口)
   data = conn.recv(1000) # Should be ready
   if data:
     print('echoing', repr(data), 'to', conn)
     conn.send(data) # Hope it won't block
   else:
     print('Client closed.', conn)
     # 将conn从监测列表删除
     sel.unregister(conn)
     conn.close()
 except ConnectionResetError:
   print('Client forcibly closed.', 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)

while True:
 '''
 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()   # 监测有无活动对象,没有就阻塞在这里等待
 for key, mask in events: # 有活动对象了
   callback = key.data   # key.data 是注册时传递的 accept 函数
   callback(key.fileobj, mask)  # key.fileobj 就是传递的 socket 对象

客户端:


import socket
tin=socket.socket()
tin.connect(('localhost',1234))
while True:
 inp=input('>>>>')
 tin.send(inp.encode('utf8'))
 data=tin.recv(1024)
 print(data.decode('utf8'))

来源:https://www.cnblogs.com/wztshine/p/12091062.html

标签:Python,select,selectors,模块
0
投稿

猜你喜欢

  • javascript 自动转到命名锚记

    2024-04-29 13:44:46
  • python新手学习使用库

    2021-06-20 13:08:38
  • Django项目如何给数据库添加约束

    2023-08-10 14:49:39
  • 使用 Python 读取电子表格中的数据实例详解

    2023-10-15 02:40:57
  • SQL Server自动更新统计信息的基本算法

    2012-10-07 11:02:50
  • 详解Google Protobuf简明教程

    2023-08-17 14:47:28
  • 一文带你学会Mysql表批量添加字段

    2024-01-22 20:03:04
  • Form Post提交容量大的数据

    2011-03-31 10:53:00
  • thinkphp框架实现删除和批量删除

    2024-06-07 15:29:22
  • linux安装python修改默认python版本方法

    2021-07-19 00:06:41
  • golang 获取字符串长度的案例

    2024-04-27 15:40:47
  • selenium与xpath之获取指定位置的元素的实现

    2022-01-14 12:02:35
  • 解决python Jupyter不能导入外部包问题

    2021-02-02 06:19:03
  • pyinstaller打包遇到的问题解决

    2021-07-22 07:56:24
  • Python里disconnect UDP套接字的方法

    2023-03-26 02:48:00
  • Mootools 1.2教程(13)——正则表达式

    2008-12-07 20:25:00
  • 如何实现删除numpy.array中的行或列

    2022-05-09 14:55:03
  • Linux yum 命令安装mysql8.0的教程详解

    2024-01-16 00:26:59
  • 附加到SQL2012的数据库就不能再附加到低于SQL2012的数据库版本的解决方法

    2024-01-27 19:46:16
  • 从Context到go设计理念轻松上手教程

    2024-05-13 10:41:07
  • asp之家 网络编程 m.aspxhome.com