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利用sklearn包编写决策树源代码
Dec 21 Python
python实现图书馆研习室自动预约功能
Apr 27 Python
Python使用selenium实现网页用户名 密码 验证码自动登录功能
May 16 Python
Python基于SMTP协议实现发送邮件功能详解
Aug 14 Python
梅尔频率倒谱系数(mfcc)及Python实现
Jun 18 Python
python 日期排序的实例代码
Jul 11 Python
Python实现打印实心和空心菱形
Nov 23 Python
python匿名函数lambda原理及实例解析
Feb 07 Python
Win10下用Anaconda安装TensorFlow(图文教程)
Jun 18 Python
Python基础教程(一)——Windows搭建开发Python开发环境
Jul 20 Python
python一些性能分析的技巧
Aug 30 Python
Python 实现PS滤镜中的径向模糊特效
Dec 03 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
150kHz到30Mhz完全冲浪手册
2020/03/20 无线电
vBulletin HACK----关于排版的两个HACK
2006/10/09 PHP
php 获取select下拉列表框的值
2010/05/08 PHP
在win7中搭建Linux+PHP 开发环境
2014/10/08 PHP
ThinkPHP实现ajax仿官网搜索功能实例
2014/12/02 PHP
PHP删除字符串中非字母数字字符方法总结
2019/01/20 PHP
PHP消息队列实现及应用详解【队列处理订单系统和配送系统】
2019/05/20 PHP
JSQL 基于客户端的成绩统计实现方法
2010/05/05 Javascript
初学js插入节点appendChild insertBefore使用方法
2011/07/04 Javascript
JS简单实现动画弹出层效果
2015/05/05 Javascript
兼容各大浏览器的JavaScript阻止事件冒泡代码
2015/07/09 Javascript
js简单网速测试方法完整实例
2015/12/15 Javascript
bootstrap实现弹窗和拖动效果
2016/01/03 Javascript
详解Javacript和AngularJS中的Promises
2016/02/09 Javascript
基于jQuery实现表格的查看修改删除
2016/08/01 Javascript
完美解决input[type=number]无法显示非数字字符的问题
2017/02/28 Javascript
详解Vue单元测试case写法
2018/05/24 Javascript
自定义Vue组件打包、发布到npm及使用教程
2019/05/22 Javascript
Elementui表格组件+sortablejs实现行拖拽排序的示例代码
2019/08/28 Javascript
python调用系统ffmpeg实现视频截图、http发送
2018/03/06 Python
python3+PyQt5使用数据库表视图
2018/04/24 Python
Python zip函数打包元素实例解析
2019/12/11 Python
Python *args和**kwargs用法实例解析
2020/03/02 Python
Windows下pycharm安装第三方库失败(通用解决方案)
2020/09/17 Python
解决Python 写文件报错TypeError的问题
2020/10/23 Python
Python3中FuzzyWuzzy库实例用法
2020/11/18 Python
Python3爬虫ChromeDriver的安装实例
2021/02/06 Python
HTML5和CSS3实例教程总结(推荐)
2016/07/18 HTML / CSS
巴西独家产品和现场演示购物网站:Shoptime
2019/07/11 全球购物
Fossil德国官网:化石手表、手袋、珠宝及配件
2019/12/07 全球购物
物流管理毕业生自荐信
2013/10/24 职场文书
财务分析个人的自荐书范文
2013/11/24 职场文书
大学生的应聘自我评价
2013/12/13 职场文书
酒店管理求职信范文
2014/04/06 职场文书
银行稽核岗位职责
2015/04/13 职场文书
毕业晚宴祝酒词
2015/08/11 职场文书