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


Posted in Python onJune 22, 2020

 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'))

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Linux下用Python脚本监控目录变化代码分享
May 21 Python
Python实现给文件添加内容及得到文件信息的方法
May 28 Python
Djang中静态文件配置方法
Jul 30 Python
利用Python实现颜色色值转换的小工具
Oct 27 Python
Python实现求解一元二次方程的方法示例
Jun 20 Python
Python生成器generator用法示例
Aug 10 Python
简单了解python PEP的一些知识
Jul 13 Python
用Python+OpenCV对比图像质量的几种方法
Jul 15 Python
Python 实现自动获取种子磁力链接方式
Jan 16 Python
Python脚本破解压缩文件口令实例教程(zipfile)
Jun 14 Python
python GUI模拟实现计算器
Jun 22 Python
vscode调试django项目的方法
Aug 06 Python
tensorflow 2.0模式下训练的模型转成 tf1.x 版本的pb模型实例
Jun 22 #Python
利用Vscode进行Python开发环境配置的步骤
Jun 22 #Python
Python Excel vlookup函数实现过程解析
Jun 22 #Python
宝塔面板成功部署Django项目流程(图文)
Jun 22 #Python
python和php哪个更适合写爬虫
Jun 22 #Python
如何理解python对象
Jun 21 #Python
什么是python的必选参数
Jun 21 #Python
You might like
PHP 分页原理分析,大家可以看看
2009/12/21 PHP
php多个文件及图片上传实例详解
2014/11/10 PHP
php字符串按照单词进行反转的方法
2015/03/14 PHP
php动态生成版权所有信息的方法
2015/03/24 PHP
PHP数组array类常见操作示例
2020/05/15 PHP
javascript 框架小结 个人工作经验
2009/06/13 Javascript
Json2Template.js 基于jquery的插件 绑定JavaScript对象到Html模板中
2011/10/29 Javascript
IE下使用cloneNode注意事项分享
2012/11/22 Javascript
带左右箭头图片轮播的JS代码
2013/12/18 Javascript
JavaScript截取字符串的2个函数介绍
2014/08/27 Javascript
JS实现Fisheye效果动感放大菜单代码
2015/10/21 Javascript
学习JavaScript设计模式之责任链模式
2016/01/18 Javascript
jquery中实现时间戳与日期相互转换
2016/04/12 Javascript
Angularjs 制作购物车功能实例代码
2016/09/14 Javascript
基于Bootstrap分页的实例讲解(必看篇)
2017/07/04 Javascript
jquery版轮播图效果和extend扩展
2017/07/18 jQuery
jquery获取transform里的值实现方法
2017/12/12 jQuery
浅析前端路由简介以及vue-router实现原理
2018/06/01 Javascript
vue + any-touch实现一个iscroll 实现拖拽和滑动动画效果
2019/04/08 Javascript
对TypeScript库进行单元测试的方法
2019/07/18 Javascript
layer弹出层自适应高度,垂直水平居中的实现
2019/09/16 Javascript
Vue + Scss 动态切换主题颜色实现换肤的示例代码
2020/04/27 Javascript
工作中常用js功能汇总
2020/11/07 Javascript
JavaScript 声明私有变量的两种方式
2021/02/05 Javascript
Python生成随机数的方法
2014/01/14 Python
详解常用查找数据结构及算法(Python实现)
2016/12/09 Python
python中Matplotlib实现绘制3D图的示例代码
2017/09/04 Python
python 图像处理画一个正弦函数代码实例
2019/09/10 Python
Python数据相关系数矩阵和热力图轻松实现教程
2020/06/16 Python
pytorch 常用函数 max ,eq说明
2020/06/28 Python
构造器Constructor是否可被override?
2013/08/06 面试题
教师远程培训感言
2014/03/06 职场文书
2016高考感言
2015/08/01 职场文书
课程设计感想范文
2015/08/11 职场文书
DIV CSS实现网页背景半透明效果
2021/12/06 HTML / CSS
微信小程序实现轮播图指示器
2022/06/25 Javascript