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 相关文章推荐
python从ftp下载数据保存实例
Nov 20 Python
python基础教程之基本内置数据类型介绍
Feb 20 Python
Python多线程编程(四):使用Lock互斥锁
Apr 05 Python
查看Python安装路径以及安装包路径小技巧
Apr 28 Python
Linux上安装Python的PIL和Pillow库处理图片的实例教程
Jun 23 Python
Python实现获取命令行输出结果的方法
Jun 10 Python
python实现人脸识别代码
Nov 08 Python
Python2实现的图片文本识别功能详解
Jul 11 Python
python字符串替换re.sub()方法解析
Sep 18 Python
在OpenCV里实现条码区域识别的方法示例
Dec 04 Python
Python 数据可视化之Seaborn详解
Nov 02 Python
Python之Matplotlib绘制热力图和面积图
Apr 13 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中多进程编程的相关函数的使用
2015/08/18 PHP
php实现网站文件批量压缩下载功能
2015/10/28 PHP
php设计模式之抽象工厂模式分析【星际争霸游戏案例】
2020/01/23 PHP
用于table内容排序
2006/07/21 Javascript
浅谈javascript 面向对象编程
2009/10/28 Javascript
js屏蔽鼠标键盘(右键/Ctrl+N/Shift+F10/F11/F5刷新/退格键)
2013/01/24 Javascript
jquery实现效果比较好的table选中行颜色
2014/03/25 Javascript
jQuery实现加入购物车飞入动画效果
2015/03/14 Javascript
根据user-agent判断蜘蛛代码黑帽跳转代码(js版与php版本)
2015/09/14 Javascript
JS+CSS相对定位实现的下拉菜单
2015/10/06 Javascript
鼠标点击input,显示瞬间的边框颜色,对之修改与隐藏实例
2016/12/26 Javascript
JS中使用正则表达式g模式和非g模式的区别
2017/04/01 Javascript
npm国内镜像 安装失败的几种解决方案
2017/06/04 Javascript
详细分析JS函数去抖和节流
2017/12/05 Javascript
深入理解Promise.all
2018/08/08 Javascript
JS集合set类的实现与使用方法示例
2019/02/01 Javascript
Vue2.0使用嵌套路由实现页面内容切换/公用一级菜单控制页面内容切换(推荐)
2019/05/08 Javascript
通过循环优化 JavaScript 程序
2019/06/24 Javascript
详解Vue中的MVVM原理和实现方法
2020/07/15 Javascript
python实现读取并显示图片的两种方法
2017/01/13 Python
Python安装及Pycharm安装使用教程图解
2019/09/20 Python
使用python-pptx包批量修改ppt格式的实现
2020/02/14 Python
python环境下安装opencv库的方法
2020/03/05 Python
推荐技术人员一款Python开源库(造数据神器)
2020/07/08 Python
基础的CSS3弹性盒Flexbox布局使用实例
2016/04/08 HTML / CSS
大专生自荐信
2013/10/04 职场文书
自主招生自荐信
2013/12/08 职场文书
给客户的道歉信
2014/01/13 职场文书
大学毕业生求职自荐信
2014/02/20 职场文书
领导接待方案
2014/03/13 职场文书
事业单位人员的自我评价范文
2014/09/21 职场文书
2015年出纳个人工作总结
2015/04/02 职场文书
JavaScript 对象创建的3种方法
2021/11/17 Javascript
R9700摩机记
2022/04/05 无线电
浅谈Node的内存泄露问题
2022/05/06 NodeJs
Redis特殊数据类型bitmap位图
2022/06/01 Redis