首先列一下,sellect、poll、epoll三者的区别

select

select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。

select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。

另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

poll

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。

poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

epoll

直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。

epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

Python select

Python的select()方法直接调用操作系统的IO接口,它监控sockets,open files, and pipes(所有带fileno()方法的文件句柄)何时变成readable 和writeable, 或者通信错误,select()使得同时监控多个连接变的简单,并且这比写一个长循环来等待和监控多客户端连接要高效,因为select直接通过操作系统提供的C的网络接口进行操作,而不是通过Python的解释器。

注意:Using Python’s file objects with select() works for Unix, but is not supported under Windows.

接下来通过echo server例子要以了解select 是如何通过单进程实现同时处理多个非阻塞的socket连接的。

import select

import socket

import sys

import queue

# Create a TCP/IP socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.setblocking(0)

# Bind the socket to the port

server_address = ('localhost', 10000)

print('starting up on %s port %s' % server_address)

server.bind(server_address)

# Listen for incoming connections

server.listen(5)

select()方法接收并监控3个通信列表, 第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接收所有要发出去的data(outgoing data),第3个监控错误信息,接下来我们需要创建2个列表来包含输入和输出信息来传给select().

# Sockets from which we expect to read

inputs = [ server ]

# Sockets to which we expect to write

outputs = [ ]

所有客户端的进来的连接和数据将会被server的主循环程序放在上面的list中处理,我们现在的server端需要等待连接可写(writable)之后才能过来,然后接收数据并返回(因此不是在接收到数据之后就立刻返回),因为每个连接要把输入或输出的数据先缓存到queue里,然后再由select取出来再发出去。

Connections are added to and removed from these lists by the server main loop. Since this version of the server is going to wait for a socket to become writable before sending any data (instead of immediately sending the reply), each output connection needs a queue to act as a buffer for the data to be sent through it.

# Outgoing message queues (socket:queue)

message_queues = {}

The main portion of the server program loops, calling select() to block and wait for network activity.

下面是此程序的主循环,调用select()时会阻塞和等待直到新的连接和数据进来

while inputs:

# Wait for at least one of the sockets to be ready for processing

print('waiting for the next event')

readable, writable, exceptional = select.select(inputs, outputs, inputs)

当你把inputs,outputs,exceptional(这里跟inputs共用)传给

select() returns three new lists, containing subsets of the contents of the lists passed in. All of the sockets in the readable list have incoming data buffered and available to be read. All of the sockets in the writable list have free space in their buffer and can be written to. The sockets returned in exceptional have had an error (the actual definition of “exceptional condition” depends on the platform).

Readable list 中的socket 可以有3种可能状态,第一种是如果这个socket是main "server" socket,它负责监听客户端的连接,如果这个main server socket出现在readable里,那代表这是server端已经ready来接收一个新的连接进来了,为了让这个main server能同时处理多个连接,在下面的代码里,我们把这个main server的socket设置为非阻塞模式。

The “readable” sockets represent three possible cases. If the socket is the main “server” socket, the one being used to listen for connections, then the “readable” condition means it is ready to accept another incoming connection. In addition to adding the new connection to the list of inputs to monitor, this section sets the client socket to not block.

# Handle inputs

for s in readable:

if s is server:

# A "readable" server socket is ready to accept a connection

connection, client_address = s.accept()

print('new connection from', client_address)

connection.setblocking(0)

inputs.append(connection)

# Give the connection a queue for data we want to send

message_queues[connection] = queue.queue()

第二种情况是这个socket是已经建立了的连接,它把数据发了过来,这个时候你就可以通过recv()来接收它发过来的数据,然后把接收到的数据放到queue里,这样你就可以把接收到的数据再传回给客户端了。

The next case is an established connection with a client that has sent data. The data is read with recv(), then placed on the queue so it can be sent through the socket and back to the client.

else:

data = s.recv(1024)

if data:

# A readable client socket has data

print('received "%s" from %s' % (data, s.getpeername()))

message_queues[s].put(data)

# Add output channel for response

if s not in outputs:

outputs.append(s)

第三种情况就是这个客户端已经断开了,所以你再通过recv()接收到的数据就为空了,所以这个时候你就可以把这个跟客户端的连接关闭了。

A readable socket without data available is from a client that has disconnected, and the stream is ready to be closed.

else:

# Interpret empty result as closed connection

print('closing', client_address, 'after reading no data')

# Stop listening for input on the connection

if s in outputs:

outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉

inputs.remove(s) #inputs中也删除掉

s.close() #把这个连接关闭掉

# Remove message queue

del message_queues[s]

对于writable list中的socket,也有几种状态,如果这个客户端连接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,否则就把这个连接从output list中移除,这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态

There are fewer cases for the writable connections. If there is data in the queue for a connection, the next message is sent. Otherwise, the connection is removed from the list of output connections so that the next time through the loop select() does not indicate that the socket is ready to send data.

# Handle outputs

for s in writable:

try:

next_msg = message_queues[s].get_nowait()

except queue.Empty:

# No messages waiting so stop checking for writability.

print('output queue for', s.getpeername(), 'is empty')

outputs.remove(s)

else:

print('sending "%s" to %s' % (next_msg, s.getpeername()))

s.send(next_msg)

最后,如果在跟某个socket连接通信过程中出了错误,就把这个连接对象在inputs\outputs\message_queue中都删除,再把连接关闭掉

# Handle "exceptional conditions"

for s in exceptional:

print('handling exceptional condition for', s.getpeername())

# Stop listening for input on the connection

inputs.remove(s)

if s in outputs:

outputs.remove(s)

s.close()

# Remove message queue

del message_queues[s]

客户端

下面的这个是客户端程序展示了如何通过select()对socket进行管理并与多个连接同时进行交互,

The example client program uses two sockets to demonstrate how the server with select() manages multiple connections at the same time. The client starts by connecting each TCP/IP socket to the server.

import socket

import sys

messages = [ 'This is the message. ',

'It will be sent ',

'in parts.',

]

server_address = ('localhost', 10000)

# Create a TCP/IP socket

socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),

socket.socket(socket.AF_INET, socket.SOCK_STREAM),

]

# Connect the socket to the port where the server is listening

prin('connecting to %s port %s' % server_address)

for s in socks:

s.connect(server_address)

接下来通过循环通过每个socket连接给server发送和接收数据。

Then it sends one pieces of the message at a time via each socket, and reads all responses available after writing new data.

for message in messages:

# Send messages on both sockets

for s in socks:

print('%s: sending "%s"' % (s.getsockname(), message))

s.send(message)

# Read responses on both sockets

for s in socks:

data = s.recv(1024)

print( '%s: received "%s"' % (s.getsockname(), data))

if not data:

print('closing socket', s.getsockname())

最后服务器端的完整代码如下:

#!/usr/bin/env python3

# -*- coding:utf-8 -*-

# Version:Python3.5.0

import select

import socket

import sys

import queue

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建一个TCP/IP socket

server.setblocking(False) # 设置非阻塞状态

server_address = ('localhost', 9999) # 设置服务器IP以及端口号

print('启动 [%s] 端口号 [%s]' % server_address)

server.bind(server_address) # 绑定IP以及端口号

server.listen(5) # 设置监听5个连接

inputs = [server, ] # 保存连接过来的连接到一个列表中

outputs = [] # 保存用来写操作的socket

message_queues = {} # 设置存放连接队列的字典

while inputs:

# 等待至少一个socket来处理

print('在等待下一个事件')

readable, writable, exceptional = select.select(inputs, outputs, inputs)

# 输入句柄

for s in readable:

if s is server: # 一个新的连接

# 一个可读的server socket 准备接收一个连接

connection, client_address = s.accept()

print('新连接来自:', client_address)

connection.setblocking(False) # 设置非阻塞

inputs.append(connection) # 把新连接添加入连接列表中

# 把这个连接添加入连接队列字典中

message_queues[connection] = queue.Queue()

else: # 有数据的连接

data = s.recv(1024) # 接收最大为1024字节的数据

if data: # 一个可读的客户端socket有数据

print('接收 "%s" 来自 %s' % (data, s.getpeername()) )

message_queues[s].put(data) # 放进一个队列里面

# Add output channel for response

if s not in outputs:

outputs.append(s) # 把s添加到output列表中去

else:

# 没有数据则关闭连接

print('没有数据过来,关闭:', client_address)

# 停止监听断开的连接

if s in outputs:

outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉

inputs.remove(s) #inputs中也删除掉

s.close() #把这个连接关闭掉

del message_queues[s] # 删除连接队列字典的数据

# 输出句柄

for s in writable:

try:

next_msg = message_queues[s].get_nowait() # 读取数据

except queue.Empty:

# No messages waiting so stop checking for writability.

print('output队列',s.getpeername(),'是空的')

outputs.remove(s)

else:

print('发送 "%s" 到 %s' % (next_msg, s.getpeername()))

s.send(next_msg) # 取到数据后发给客户端

# Handle "exceptional conditions"

for s in exceptional:

print('handling exceptional condition for', s.getpeername() )

inputs.remove(s) # 停止监控input 连接

if s in outputs:

outputs.remove(s)

s.close()

del message_queues[s] # 删除连接队列

客户端的完整代码如下:

#!/usr/bin/env python3

# -*- coding:utf-8 -*-

# Version:Python3.5.0

import socket

import sys

messages = ['This is the message.',

'It will be sent.',

'in parts.']

server_address = ('localhost', 9999) # 设置服务器IP以及端口号

# 创建TCP/IP socket 列表,总共50个连接

socks =[socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(50)]

print('连接到服务器:%s 端口号:%s' % server_address)

for s in socks:

s.connect(server_address) # 连接socks列表中的所有socket

for msg in messages:

# 所有socket发送信息到服务器

for s in socks:

print(s.getsockname(), ': 发送数据:"%s"' % msg)

s.send(bytes(msg, 'utf8'))

# 接收服务器返回的信息

for s in socks:

data = s.recv(1024) # 设置接收大小为1024字节

print('%s: 接收数据:"%s"' % (s.getsockname(), data.decode()))

if not data: # 没有接收到数据,说明接收完毕

print('关闭socket', s.getsockname())

s.close() # 关闭socket

执行结果如下

服务器:

启动 [localhost] 端口号 [9999]

在等待下一个事件

新连接来自: ('127.0.0.1', 60680)

在等待下一个事件

新连接来自: ('127.0.0.1', 60681)

在等待下一个事件

新连接来自: ('127.0.0.1', 60682)

在等待下一个事件

新连接来自: ('127.0.0.1', 60683)

在等待下一个事件

新连接来自: ('127.0.0.1', 60684)

在等待下一个事件

接收 "b'This is the message.'" 来自 ('127.0.0.1', 60680)

接收 "b'This is the message.'" 来自 ('127.0.0.1', 60681)

在等待下一个事件

接收 "b'This is the message.'" 来自 ('127.0.0.1', 60682)

接收 "b'This is the message.'" 来自 ('127.0.0.1', 60683)

接收 "b'This is the message.'" 来自 ('127.0.0.1', 60684)

发送 "b'This is the message.'" 到 ('127.0.0.1', 60680)

发送 "b'This is the message.'" 到 ('127.0.0.1', 60681)

在等待下一个事件

output队列 ('127.0.0.1', 60680) 是空的

output队列 ('127.0.0.1', 60681) 是空的

发送 "b'This is the message.'" 到 ('127.0.0.1', 60682)

发送 "b'This is the message.'" 到 ('127.0.0.1', 60683)

发送 "b'This is the message.'" 到 ('127.0.0.1', 60684)

在等待下一个事件

output队列 ('127.0.0.1', 60682) 是空的

output队列 ('127.0.0.1', 60683) 是空的

output队列 ('127.0.0.1', 60684) 是空的

在等待下一个事件

接收 "b'It will be sent.'" 来自 ('127.0.0.1', 60680)

接收 "b'It will be sent.'" 来自 ('127.0.0.1', 60681)

在等待下一个事件

接收 "b'It will be sent.'" 来自 ('127.0.0.1', 60682)

接收 "b'It will be sent.'" 来自 ('127.0.0.1', 60683)

接收 "b'It will be sent.'" 来自 ('127.0.0.1', 60684)

发送 "b'It will be sent.'" 到 ('127.0.0.1', 60680)

发送 "b'It will be sent.'" 到 ('127.0.0.1', 60681)

在等待下一个事件

output队列 ('127.0.0.1', 60680) 是空的

output队列 ('127.0.0.1', 60681) 是空的

发送 "b'It will be sent.'" 到 ('127.0.0.1', 60682)

发送 "b'It will be sent.'" 到 ('127.0.0.1', 60683)

发送 "b'It will be sent.'" 到 ('127.0.0.1', 60684)

在等待下一个事件

output队列 ('127.0.0.1', 60682) 是空的

output队列 ('127.0.0.1', 60683) 是空的

output队列 ('127.0.0.1', 60684) 是空的

在等待下一个事件

接收 "b'in parts.'" 来自 ('127.0.0.1', 60680)

在等待下一个事件

接收 "b'in parts.'" 来自 ('127.0.0.1', 60681)

发送 "b'in parts.'" 到 ('127.0.0.1', 60680)

在等待下一个事件

接收 "b'in parts.'" 来自 ('127.0.0.1', 60682)

接收 "b'in parts.'" 来自 ('127.0.0.1', 60683)

接收 "b'in parts.'" 来自 ('127.0.0.1', 60684)

output队列 ('127.0.0.1', 60680) 是空的

发送 "b'in parts.'" 到 ('127.0.0.1', 60681)

在等待下一个事件

output队列 ('127.0.0.1', 60681) 是空的

发送 "b'in parts.'" 到 ('127.0.0.1', 60682)

发送 "b'in parts.'" 到 ('127.0.0.1', 60683)

发送 "b'in parts.'" 到 ('127.0.0.1', 60684)

在等待下一个事件

output队列 ('127.0.0.1', 60682) 是空的

output队列 ('127.0.0.1', 60683) 是空的

output队列 ('127.0.0.1', 60684) 是空的

在等待下一个事件

没有数据过来,关闭: ('127.0.0.1', 60684)

在等待下一个事件

没有数据过来,关闭: ('127.0.0.1', 60684)

在等待下一个事件

没有数据过来,关闭: ('127.0.0.1', 60684)

没有数据过来,关闭: ('127.0.0.1', 60684)

在等待下一个事件

没有数据过来,关闭: ('127.0.0.1', 60684)

在等待下一个事件

客户端:

连接到服务器:localhost 端口号:9999

('127.0.0.1', 60680) : 发送数据:"This is the message."

('127.0.0.1', 60681) : 发送数据:"This is the message."

('127.0.0.1', 60682) : 发送数据:"This is the message."

('127.0.0.1', 60683) : 发送数据:"This is the message."

('127.0.0.1', 60684) : 发送数据:"This is the message."

('127.0.0.1', 60680): 接收数据:"This is the message."

('127.0.0.1', 60681): 接收数据:"This is the message."

('127.0.0.1', 60682): 接收数据:"This is the message."

('127.0.0.1', 60683): 接收数据:"This is the message."

('127.0.0.1', 60684): 接收数据:"This is the message."

('127.0.0.1', 60680) : 发送数据:"It will be sent."

('127.0.0.1', 60681) : 发送数据:"It will be sent."

('127.0.0.1', 60682) : 发送数据:"It will be sent."

('127.0.0.1', 60683) : 发送数据:"It will be sent."

('127.0.0.1', 60684) : 发送数据:"It will be sent."

('127.0.0.1', 60680): 接收数据:"It will be sent."

('127.0.0.1', 60681): 接收数据:"It will be sent."

('127.0.0.1', 60682): 接收数据:"It will be sent."

('127.0.0.1', 60683): 接收数据:"It will be sent."

('127.0.0.1', 60684): 接收数据:"It will be sent."

('127.0.0.1', 60680) : 发送数据:"in parts."

('127.0.0.1', 60681) : 发送数据:"in parts."

('127.0.0.1', 60682) : 发送数据:"in parts."

('127.0.0.1', 60683) : 发送数据:"in parts."

('127.0.0.1', 60684) : 发送数据:"in parts."

('127.0.0.1', 60680): 接收数据:"in parts."

('127.0.0.1', 60681): 接收数据:"in parts."

('127.0.0.1', 60682): 接收数据:"in parts."

('127.0.0.1', 60683): 接收数据:"in parts."

('127.0.0.1', 60684): 接收数据:"in parts."

python select模块_Python之select模块解析相关推荐

  1. python getopt使用_Python命令行参数解析模块getopt使用实例

    这篇文章主要介绍了Python命令行参数解析模块getopt使用实例,本文讲解了使用语法格式.短选项参数实例.长选项参数实例等内容,需要的朋友可以参考下 格式 getopt(args, options ...

  2. python psycopg2使用_Python中用psycopg2模块操作PostgreSQL方法

    其实在Python中可以用来连接PostgreSQL的模块很多,这里比较推荐psycopg2.psycopg2安装起来非常的简单(pip install psycopg2),这里主要重点介绍下如何使用 ...

  3. python 最好用的数据库模块_Python 使用pymysql模块操作数据库

    Python 中操作 MySQL 步骤 看完了上面的这个操作流程,那么python操作数据库可以用上面模块来操作呢? 目前比较流行的就是pymysql,下面来看看介绍. pymysql介绍PyMySQ ...

  4. python keys模块_python自动化常用模块

    python自动化常用函数 导入time模块 import time 导入ActionChains模块(鼠标) from selenium.webdriver.common.action_chains ...

  5. python中自带的模块_python中的模块详解

    概念 python中的模块是什么?简而言之,在python中,一个文件(以".py"为后缀名的文件)就叫做一个模块,每一个模块在python里都被看做是一个独立的文件.模块可以被项 ...

  6. 简述python中怎样导入模块_Python中导入模块的两种模式,import

    import import pandas import pandas as pd 使用函数方式:.(),或者.() 比如 pandas.read_csv("data/stock.csv&qu ...

  7. python必学的模块_Python常用的模块

    模块和包 1.1模块介绍模块定义:一系列功能的集合体 模块使用: import导入模块 或者 from ... import... 导入模块 模块分类:内置模块 自定义模块 第三方模块 模块加载顺序: ...

  8. python的窗口处理模块_python的图像处理模块

    除了opencv专门用来进行图像处理,可以进行像素级.特征级.语义级.应用级的图像处理外,python中还有其他库用来进行简单的图像处理,比如图像的读入和保存.滤波.直方图均衡等简单的操作,下面对这些 ...

  9. python安装os模块_python的os模块(ipython,文件,目录,权限,管理)

    什么是os模块 os模块提供了多数操作系统的功能接口函数.当os模块被导入后,它会自适应于不同的操作系统平台,根据不同的平台进行相应的操作,在python编程时,经常和文件.目录打交道,这时就离不了o ...

最新文章

  1. 使用Telnet与ssh协议远程登录linux系统
  2. Google发布了Tensorflow Lite,用于移动电话的神经网络库
  3. 建立表/索引时的 存储参数:【PCTUSED参数与PCTFREE参数】
  4. 使用命令行导出 SQL Server 数据层应用程序
  5. [重拾Oracle - 00]既然青春留不住,那么还是要学习-----工具:在线Oracle(Oracle Live SQL)...
  6. c4503文件服务器,理光C3503/C4503/C5503检查状态下各项目说明解释
  7. 06-07 Jenkins中配置 Git 认证信息
  8. spss统计分析基础教程 SPSS统计分析从入门到精通光盘
  9. 自媒体全套教程+全套工具(带教程)+原创实操教程
  10. 用java语言写出青蛙的身份_第三章 JAVA 语言基础
  11. 微信群网址活码在线生成系统源码 二维码活码生成
  12. 【爬虫实战】自制属于你自己的在线翻译程序 - 爬取 360 在线翻译(教学 + 实践)
  13. Lenovo Y50-70 1080p Hackintosh Catalina
  14. 互联网+大赛作品_“颂中国力量 绘美好梦想”全市中小学生互联网+书画大赛作品展示(四)...
  15. APM,云时代的应用性能优化神器
  16. [Jzoj] 3426. 封印一击
  17. python 最大子序列之和
  18. Matplotlib Error: meta NOT subset: don‘t know howto subset; dropped
  19. 电脑系统还原怎么操作?
  20. QT与OGRE做3D地图编辑器

热门文章

  1. Ado.Net实现简易(省、市、县)三级联动查询,还附加Access数据
  2. sql数据库相关语句
  3. ECShop显示某商品销售累计
  4. 使用sizeof计算类的大小
  5. Ubuntu编译安装VASP
  6. 情商(实践版本,丹尼尔.戈尔曼)---阅读记录
  7. 修改framework后重新刷入手机
  8. LCD 常用的客观效果指标和测试方法
  9. WINCE6.0添加微软简体中文输入法3.0
  10. python注释_Python的注释用法