Python的select()方法直接调用操作系统的IO接口,监听socket、打开的文件、管道(所有文件句柄用fileno()方法)什么时候变成可读可写,或者当出现通信错误时,select()使同时监控多个连接更简单,而且比写一个长循环等待和监控多个客户端连接效率更高,因为select直接通过操作系统提供的C网络接口进行操作,而不是通过Python解释器。
注意:Python的select()方法适用于UNIX操作系统,不支持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 >>sys.stderr, 'starting up on %s port %s' % server_address
server.bind(server_address)
# Listen for incoming connections
server.listen(5)
select() 方法接收并监控三个通信列表。第一个是所有输入数据,指的是从外部发送的数据。第二个是监视和接收所有要发送的数据(传出数据),第三个是监视错误消息,接下来我们需要创建 2 个列表来包含输入和输出信息以传递给 select()。
# Sockets from which we expect to read
inputs = [ server ]
# Sockets to which we expect to write
outputs = [ ]
所有来自客户端的传入连接和数据都将由上面列表中的服务器主循环程序处理。我们当前的服务器端需要等待连接可写才能过来,然后接收数据并返回(所以不是在接收到数据后立即返回),因为每个连接都要缓存输入或输出数据先在队列中,然后取出并通过select发送。
# Outgoing message queues (socket:Queue)
message_queues = {}
通过服务器主循环在这些列表中添加和删除连接。由于此版本的服务器在发送任何数据之前将等待套接字变为可写(而不是立即发送回复),因此每个传出连接都需要一个队列作为要通过它发送的数据的缓冲区。
下面是这个程序的主循环,调用 select() 阻塞并等待直到有新的连接和数据进来。
# Wait for at least one of the sockets to be ready for processing
print >>sys.stderr, '\nwaiting for the next event'
readable, writable, exceptional = select.select(inputs, outputs, inputs)
当你将输入、输出、异常(在此处与输入共享)传递给 select() 时,它会返回 3 个新列表,我们在上面分别指定为可读。
writable,exception,可读列表中的所有socket连接代表可以接收(recv)的数据,可写列表中的所有socket连接都可以发送(send)操作,当连接通信错误发生时向异常列表写入错误。
可读列表中的套接字可以有 3 种可能的状态。首先是如果套接字是主“服务器”套接字,它负责监视客户端的连接。如果主服务器套接字出现在可读中,则表示服务器已准备好接收新连接。为了让主服务器同时处理多个连接,在下面的代码中,我们将主服务器的socket设置为非阻塞模式。
# 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 >>sys.stderr, '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()来接收它发送的数据,然后将接收到的数据放入队列中,这样就可以将接收到的数据传回给客户端了。
else:
data = s.recv(1024)
if data:
# A readable client socket has data
print >>sys.stderr, '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()收到的数据是空的,此时可以关闭与客户端的连接。
else:
# Interpret empty result as closed connection
print >>sys.stderr, 'closing', client_address, 'after reading no data'
# Stop listening for input on the connection
if s in outputs:
output.remove(s) #由于客户端断开连接,我不需要给它返回数据,所以如果此时这个客户端的连接对象还在输出列表中,删除它。
inputs.remove(s) #inputs中也删除掉
s.close() #把这个连接关闭掉
# Remove message queue
del message_queues[s]
对于可写列表中的套接字,也有几种状态。如果客户端连接在其对应的队列中有数据,则将数据取出并发送回客户端,否则从输出列表中删除该连接。删除,以便下次调用循环 select() 并检测到输出列表中没有此类连接时,它将认为该连接处于非活动状态。
# 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 >>sys.stderr, 'output queue for', s.getpeername(), 'is empty'
outputs.remove(s)
else:
print >>sys.stderr, '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 >>sys.stderr, '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]
本文为原创文章,版权归知行编程网所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 如何在python3线程中使用Event?12/31
- ♥ 如何使用 pip 运行 python10/31
- ♥ mac卸载python3.6的方法11/23
- ♥ python3.6中是否有pip11/06
- ♥ python第三方库在哪里09/28
- ♥ Python如何将图像转换为base64编码11/15
内容反馈