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基于socket实现网络广播的方法
Apr 29 Python
浅谈Python 对象内存占用
Jul 15 Python
Tornado协程在python2.7如何返回值(实现方法)
Jun 22 Python
详解Python数据可视化编程 - 词云生成并保存(jieba+WordCloud)
Mar 26 Python
Python实现的统计文章单词次数功能示例
Jul 08 Python
Python List列表对象内置方法实例详解
Oct 22 Python
python脚本后台执行方式
Dec 21 Python
Python list运算操作代码实例解析
Jan 20 Python
基于Python数据分析之pandas统计分析
Mar 03 Python
Python如何在单元测试中给对象打补丁
Aug 03 Python
QT5 Designer 打不开的问题及解决方法
Aug 20 Python
Python+DeOldify实现老照片上色功能
Jun 21 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后台多用户权限组思路与实现程序代码分享
2012/02/13 PHP
ThinkPHP3.1新特性之命名范围的使用
2014/06/19 PHP
PHP+shell脚本操作Memcached和Apache Status的实例分享
2016/03/11 PHP
文本框回车提交与禁止提交示例
2013/09/27 Javascript
jQuery中innerHeight()方法用法实例
2015/01/19 Javascript
DOM基础教程之事件类型
2015/01/20 Javascript
谈谈我对JavaScript原型和闭包系列理解(随手笔记6)
2015/12/20 Javascript
js enter键激发事件实例代码
2016/08/17 Javascript
JavaScript构建自己的对象示例
2016/11/29 Javascript
javascript判断元素存在和判断元素存在于实时的dom中的方法
2017/01/17 Javascript
详解angularjs利用ui-route异步加载组件
2017/05/21 Javascript
如何编写一个d.ts文件的步骤详解
2018/04/13 Javascript
微信小程序实现团购或秒杀批量倒计时
2020/11/01 Javascript
webpack开发环境和生产环境的深入理解
2018/11/08 Javascript
ES6入门教程之let、const的使用方法
2019/04/13 Javascript
Python Web框架Flask中使用百度云存储BCS实例
2015/02/08 Python
PyCharm在win10的64位系统安装实例
2017/11/26 Python
Python面向对象之反射/自省机制实例分析
2018/08/24 Python
python使用writerows写csv文件产生多余空行的处理方法
2019/08/01 Python
Django数据库操作之save与update的使用
2020/04/01 Python
使用keras根据层名称来初始化网络
2020/05/21 Python
python3爬虫中异步协程的用法
2020/07/10 Python
AmazeUI 单选框和多选框的实现示例
2020/08/18 HTML / CSS
美国摄影爱好者购物网站:Focus Camera
2016/10/21 全球购物
红色连衣裙精品店:Red Dress Boutique
2018/08/11 全球购物
外贸采购员求职的自我评价
2013/11/26 职场文书
船舶专业个人求职信范文
2014/01/02 职场文书
网上书店创业计划书
2014/01/12 职场文书
期中考试后的反思
2014/02/08 职场文书
洗发露广告词
2014/03/14 职场文书
基本公共卫生服务健康教育工作方案
2014/05/22 职场文书
面试自我评价范文
2014/09/17 职场文书
师德师风个人整改措施
2014/10/27 职场文书
2014年销售内勤工作总结
2014/12/01 职场文书
安全生产协议书
2016/03/22 职场文书
关于flex 上下文中自动 margin的问题(完整例子)
2021/05/20 HTML / CSS