深入理解python中的select模块

简介

Python中的select模块专注于I/O多路复用,提供了select  poll  epoll三个方法(其中后两个在Linux中可用,windows仅支持select),另外也提供了kqueue方法(freeBSD系统)

select方法

进程指定内核监听哪些文件描述符(最多监听1024个fd)的哪些事件,当没有文件描述符事件发生时,进程被阻塞;当一个或者多个文件描述符事件发生时,进程被唤醒。

当我们调用select()时:

  1、上下文切换转换为内核态

  2、将fd从用户空间复制到内核空间

  3、内核遍历所有fd,查看其对应事件是否发生

  4、如果没发生,将进程阻塞,当设备驱动产生中断或者timeout时间后,将进程唤醒,再次进行遍历

  5、返回遍历后的fd

  6、将fd从内核空间复制到用户空间

fd:file descriptor 文件描述符

fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout])

参数: 可接受四个参数(前三个必须)

  • rlist: wait until ready for reading
  • wlist: wait until ready for writing
  • xlist: wait for an “exceptional condition”
  • timeout: 超时时间

返回值:三个列表

select方法用来监视文件描述符(当文件描述符条件不满足时,select会阻塞),当某个文件描述符状态改变后,会返回三个列表

1、当参数1 序列中的fd满足“可读”条件时,则获取发生变化的fd并添加到fd_r_list中

2、当参数2 序列中含有fd时,则将该序列中所有的fd添加到 fd_w_list中

3、当参数3 序列中的fd发生错误时,则将该发生错误的fd添加到 fd_e_list中

4、当超时时间为空,则select会一直阻塞,直到监听的句柄发生变化

当超时时间 = n(正整数)时,那么如果监听的句柄均无任何变化,则select会阻塞n秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。

实例:利用select实现一个可并发的服务端

import socket
import select

s = socket.socket()
s.bind(('127.0.0.1',8888))
s.listen(5)
r_list = [s,]
num = 0
while True:
 rl, wl, error = select.select(r_list,[],[],10)
 num+=1
 print('counts is %s'%num)
 print("rl's length is %s"%len(rl))
 for fd in rl:
  if fd == s:
   conn, addr = fd.accept()
   r_list.append(conn)
   msg = conn.recv(200)
   conn.sendall(('first----%s'%conn.fileno()).encode())
  else:
   try:
    msg = fd.recv(200)
    fd.sendall('second'.encode())
   except ConnectionAbortedError:
    r_list.remove(fd)

s.close()
import socket

flag = 1
s = socket.socket()
s.connect(('127.0.0.1',8888))
while flag:
 input_msg = input('input>>>')
 if input_msg == '0':
  break
 s.sendall(input_msg.encode())
 msg = s.recv(1024)
 print(msg.decode())

s.close()

在服务端我们可以看到,我们需要不停的调用select, 这就意味着:

  1  当文件描述符过多时,文件描述符在用户空间与内核空间进行copy会很费时

  2  当文件描述符过多时,内核对文件描述符的遍历也很浪费时间

  3  select最大仅仅支持1024个文件描述符

poll与select相差不大,本文不作介绍

epoll方法:

epoll很好的改进了select:

  1、epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时,会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

  2、epoll会在epoll_ctl时把指定的fd遍历一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd

  3、epoll对文件描述符没有额外限制

select.epoll(sizehint=-1, flags=0) 创建epoll对象

epoll.close()
Close the control file descriptor of the epoll object.关闭epoll对象的文件描述符

epoll.closed
True if the epoll object is closed.检测epoll对象是否关闭

epoll.fileno()
Return the file descriptor number of the control fd.返回epoll对象的文件描述符

epoll.fromfd(fd)
Create an epoll object from a given file descriptor.根据指定的fd创建epoll对象

epoll.register(fd[, eventmask])
Register a fd descriptor with the epoll object.向epoll对象中注册fd和对应的事件

epoll.modify(fd, eventmask)
Modify a registered file descriptor.修改fd的事件

epoll.unregister(fd)
Remove a registered file descriptor from the epoll object.取消注册

epoll.poll(timeout=-1, maxevents=-1)
Wait for events. timeout in seconds (float)阻塞,直到注册的fd事件发生,会返回一个dict,格式为:{(fd1,event1),(fd2,event2),……(fdn,eventn)}

事件:

EPOLLIN Available for read 可读 状态符为1
EPOLLOUT Available for write 可写 状态符为4
EPOLLPRI Urgent data for read
EPOLLERR Error condition happened on the assoc. fd 发生错误 状态符为8
EPOLLHUP Hang up happened on the assoc. fd 挂起状态
EPOLLET Set Edge Trigger behavior, the default is Level Trigger behavior 默认为水平触发,设置该事件后则边缘触发
EPOLLONESHOT Set one-shot behavior. After one event is pulled out, the fd is internally disabled
EPOLLRDNORM Equivalent to EPOLLIN
EPOLLRDBAND Priority data band can be read.
EPOLLWRNORM Equivalent to EPOLLOUT
EPOLLWRBAND Priority data may be written.
EPOLLMSG Ignored.

水平触发和边缘触发:

Level_triggered(水平触发,有时也称条件触发):当被监控的文件描述符上有可读写事件发生时,epoll.poll()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll.poll()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!! 优点很明显:稳定可靠

Edge_triggered(边缘触发,有时也称状态触发):当被监控的文件描述符上有可读写事件发生时,epoll.poll()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll.poll()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!缺点:某些条件下不可靠

epoll实例:

import socket
import select

s = socket.socket()
s.bind(('127.0.0.1',8888))
s.listen(5)
epoll_obj = select.epoll()
epoll_obj.register(s,select.EPOLLIN)
connections = {}
while True:
 events = epoll_obj.poll()
 for fd, event in events:
  print(fd,event)
  if fd == s.fileno():
   conn, addr = s.accept()
   connections[conn.fileno()] = conn
   epoll_obj.register(conn,select.EPOLLIN)
   msg = conn.recv(200)
   conn.sendall('ok'.encode())
  else:
   try:
    fd_obj = connections[fd]
    msg = fd_obj.recv(200)
    fd_obj.sendall('ok'.encode())
   except BrokenPipeError:
    epoll_obj.unregister(fd)
    connections[fd].close()
    del connections[fd]

s.close()
epoll_obj.close()
import socket

flag = 1
s = socket.socket()
s.connect(('127.0.0.1',8888))
while flag:
 input_msg = input('input>>>')
 if input_msg == '0':
  break
 s.sendall(input_msg.encode())
 msg = s.recv(1024)
 print(msg.decode())

s.close()

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

时间: 2017-04-20

Python中使用select模块实现非阻塞的IO

Socket的英文原义是"孔"或"插座".作为BSD UNIX的进程通信机制,取后一种意思.通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.在Internet上的主机一般运行了多个服务软件,同时提供几种服务.每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务.Socket正如其英文原意那样,像一个多孔插座.一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110

Python通过select实现异步IO的方法

本文实例讲述了Python通过select实现异步IO的方法.分享给大家供大家参考.具体如下: 在Python中使用select与poll比起在C中使用简单得多.select函数的参数是3个列表,包含整数文件描述符,或者带有可返回文件描述符的fileno()方法对象.第一个参数是需要等待输入的对象,第二个指定等待输出的对象,第三个参数指定异常情况的对象.第四个参数则为设置超时时间,是一个浮点数.指定以秒为单位的超时值.select函数将会返回一组文件描述符,包括输入,输出以及异常. 在linux

基于python select.select模块通信的实例讲解

要理解select.select模块其实主要就是要理解它的参数, 以及其三个返回值. select()方法接收并监控3个通信列表, 第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接收所有要发出去的data(outgoing data),第3个监控错误信息在网上一直在找这个select.select的参数解释, 但实在是没有, 哎...自己硬着头皮分析了一下. readable, writable, exceptional = select.select(inputs, ou

Python基于select实现的socket服务器

本文实例讲述了Python基于select实现的socket服务器.分享给大家供大家参考,具体如下: 借鉴了asyncore模块中select.select的使用方法 import socket import traceback import select EOL1 = b'\n\n' EOL2 = b'\n\r\n' socketmap = {} r,w,e = [],[],[] response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:

Python基于Socket实现的简单聊天程序示例

本文实例讲述了Python基于Socket实现的简单聊天程序.分享给大家供大家参考,具体如下: 需求:SCIENCE 和MOOD两个人软件专业出身,厌倦了大众化的聊天软件,想着自己开发一款简易的聊天软件,满足他们的个性化需求,又不失"专业水准",Talk is easy, try to code it. 技术:socket,详细可参考前文:Python Socket实现简单TCP Server/client功能 语言:python 尽管socket区分服务器和客户端,但是在聊天程序中两

Python基于socket实现简单的即时通讯功能示例

本文实例讲述了Python基于socket实现简单的即时通讯功能.分享给大家供大家参考,具体如下: 客户端tcpclient.py # -*- coding: utf-8 -*- import socket import threading # 目标地址IP/URL及端口 target_host = "127.0.0.1" target_port = 9999 # 创建一个socket对象 client = socket.socket(socket.AF_INET,socket.SOC

Python基于Socket实现简单聊天室

本文实例为大家分享了Python基于Socket实现简单聊天室,供大家参考,具体内容如下 服务端 #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2017/7/27 15:34 # @File : Server.py """ 聊天室服务端 """ import socket,select host='' port=7799 addr=(host,port) inputs=[] mem

Python基于twisted实现简单的web服务器

本文实例讲述了Python基于twisted实现简单的web服务器,分享给大家供大家参考.具体方法如下: 1. 新建htm文件夹,在这个文件夹中放入显示的网页文件 2. 在htm文件夹的同级目录下,建立web.py,web.py的内容为: from twisted.web.resource import Resource from twisted.web import server from twisted.web import static from twisted.internet impo

python基于socket实现网络广播的方法

本文实例讲述了python基于socket实现网络广播的方法.分享给大家供大家参考.具体实现方法如下: import socket, sys dest = ('<broadcast>', 51423) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) s.sendto("Hi", dest) print &qu

Python基于socket模块实现UDP通信功能示例

本文实例讲述了Python基于socket模块实现UDP通信功能.分享给大家供大家参考,具体如下: 一 代码 1.接收端 import socket #使用IPV4协议,使用UDP协议传输数据 s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #绑定端口和端口号,空字符串表示本机任何可用IP地址 s.bind(('', 5000)) while True: data, addr=s.recvfrom(1024) #显示接收到的内容 print(

python基于socket进行端口转发实现后门隐藏的示例

思想: 用户正常浏览器访问请求通过8080端口,请求若为http请求,则正常转发到80端口保证网站正常运行.否则转发到8888端口执行系统命令. 8888端口监听代码: #!/usr/bin/env python from socket import * import os HOST='127.0.0.1' PORT=8888 BUFSIZE=1024 ADDR=(HOST,PORT) tcpSerSock = socket(AF_INET,SOCK_STREAM) tcpSerSock.set

python基于socket实现的UDP及TCP通讯功能示例

本文实例讲述了python基于socket实现的UDP及TCP通讯功能.分享给大家供大家参考,具体如下: Server: import socket address = ('127.0.0.1', 31500) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(address) while True: data, addr = s.recvfrom(2048) if not data: print "client has ex

python基于socket函数实现端口扫描

本文实例为大家分享了python基于socket实现端口扫描的具体代码,供大家参考,具体内容如下 自学Python一段时间,写个端口扫描器练练手.有什么不足之处,请见谅 这是基于socket函数对端口进行端口扫描 所以,首先要导入socket模块 from socket import * 其次,通过正侧表达式来判断输入的是不是IP地址 compile_ip = re.compile( '^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4