首先列一下,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()后,它返回3个新的list,我们上面将他们分别赋值为readable,writable,exceptional, 所有在readable list中的socket连接代表有数据可接收(recv),所有在writable list中的存放着你可以对其进行发送(send)操作的socket连接,当连接通信出现error时会把error写到exceptional列表中。

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模块解析的更多相关文章

  1. Python中pandas模块解析

    Pandas基于两种数据类型: series 与 dataframe . 1.Series 一个series是一个一维的数据类型,其中每一个元素都有一个标签.类似于Numpy中元素带标签的数组.其中, ...

  2. Python中matplotlib模块解析

    用Matplotlib绘制二维图像的最简单方法是: 1.  导入模块 导入matplotlib的子模块 import matplotlib.pyplot as plt import numpy as ...

  3. Python中csv模块解析

    导入模块 import csv 2.读取csv文件 file1 = open('test1.csv', 'rb') reader = csv.reader(file1) rows = [row for ...

  4. Python中xlrd模块解析

    xlrd 导入模块 import xlrd 2.打开指定的excel文件,返回一个data对象 data = xlrd.open_workbook(file)                     ...

  5. python xml.dom模块解析xml

    1. 什么是xml?有何特征? xml即可扩展标记语言,它可以用来标记数据.定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言. 例子:del.xml <?xml version=&q ...

  6. Python 通过sgmllib模块解析HTML

    """ 对html文本的解析方案-示例:在标签开始的时候检查标签中的attrs属性,解析出所有的参数的href属性值 依赖安装:pip install sgmllib3k ...

  7. python利用select实现的Socket Server

    # 利用python的select模块实现简单的Socket Sever #实现多用户访问,再次基础上可以实现FTP Server应用程序 # 发布目的,在于解决了客户端强行终止时,服务器端也跟着程序 ...

  8. python中的select模块

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

  9. python select模块详解

    要理解select.select模块其实主要就是要理解它的参数, 以及其三个返回值.select()方法接收并监控3个通信列表, 第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接 ...

随机推荐

  1. 内存保护机制及绕过方案——从堆中绕过safeSEH

    1.1    SafeSEH内存保护机制 1.1.1    Windows异常处理机制 Windows中主要两种异常处理机制,Windows异常处理(VEH.SEH)和C++异常处理.Windows异 ...

  2. linux下vim对于意外退出的文档的再次开启

    转载的: 1.对于同一个文件如果上次已经打开,而未关闭的情况下,又打开该文件进行编辑时,会出现如下提醒: 这是由于已经打开但未闭关的文件,会在其目录下出现一个.swp的文件,由于是属于隐藏文件,可以用 ...

  3. Apk大瘦身

    Android的apk文件越来越大了这已经是一个不争的事实.在Android 还是最初版本的时候,一个app的apk文件大小也还只有2 MB左右,到了现在,一个app的apk文件大小已经升级到10MB ...

  4. .Net版SQLite无法访问网络位置的数据库文件-winOpen,os_win.c 36702异常

    最近一个C#小程序,希望将SQLite数据库放在网络共享的位置,让多个客户端同时访问.却发现SQLite连接不上该网络位置的数据库,而如果数据库在本地则一切正常. 例如将SQLite数据库 test. ...

  5. 一个 token 控件

    用于以分词形式显示某个对象的多个标签,比如: 用法 将 TagsView.h/.m 文件拷贝到你的项目文件夹,在需要用到该控件的地方导入 TagsView.h 头文件. IB 中的工作 拖一个 UIV ...

  6. 热烈祝贺博主LZUGIS博客访问量突破

    截止发文时间,博主"LZUGIS"CSDN博客文章总访问量突破50W,值此特殊的时刻,特发此文,以表纪念与督促. 博客详情 博客专栏 公众号 常言道:不积跬步,无以至千里:不积小流 ...

  7. iOS开发之Documentation.build/Script-BC552B3A15.sh:

    /Users/hbbhao/Library/Developer/Xcode/DerivedData/AWLive-dmbegyhgamayzudqqdentwngdpkr/Build/Intermed ...

  8. SQL中合并两个表的JOIN语句

    SQL里有四种JOIN语句用于根据某条件合并两个表: (INNER) JOIN: 交集 LEFT (OUTER) JOIN: 左表数据全包括,右表对应的如果没有就是NULL RIGHT (OUTER) ...

  9. svn的使用流程

    一.安装: 1. 服务器端:VisualSVN_Server 2. 客户端:TortoiseSVN 二.使用VisualSVN Server建立版本库 1. 首先打开VisualSVN Server ...

  10. Requst Servervariables

    Request.ServerVariables("Url") 返回服务器地址 Request.ServerVariables("Path_Info") 客户端提 ...