11.4.3 使用poll()
poll()函数提供的特性与select()类似,不过底层实现更为有效。其缺点是windows不支持poll(),所以使用poll()的程序可移植性较差。
利用poll()建立的回送服务器最前面与其他例子一样,也使用了相同的套接字配置代码。
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 t othe port.
server_address = ('localhost',10000)
print('starting up on {} port {}'.format(*server_address),
file=sys.stderr)
server.bind(server_address)
# Listen for incoming connections.
server.listen(5)
# Keep up the queues of outgoing messages.
message_queues = {}
# 传入poll()的超时值用毫秒表示,而不是秒,所以如果要暂停1秒,则超时值必须设置
# 为1000。
# Do not block forever (milliseconds).
TIMEOUT = 1000
# Python用一个类实现poll(),有这个类管理所监视的注册的数据通道。通过调用
# register()增加通道,同时利用标志指示该通道对哪些事件感兴趣。
# 回送服务器将建立一些只读的套接字,以及另外一些可以读写的套接字。局部变量
# READ_ONLY和READ_WRITE中保存了相应的标志组合。
# Commonly used flag sets
READ_ONLY = (
select.POLLIN |
select.POLLPRI |
select.POLLHUP |
select.POLLERR
)
READ_WRITE = READ_ONLY | select.POLLOUT
# 这里注册了server套接字,入站连接或数据会触发一个事件。
# Set up the poller.
poller = select.poll()
poller.register(server,READ_ONLY)
# 由于poll()返回一个元组列表,元组中包含套接字的文件描述符和事件标志,因此需要
# 一个文件描述符编号到对象的映射来获取socket,以便读取或写入。
# Map file descriptors to socket objects.
fd_to_socket = {
server.fileno():server,
}
# 服务器的循环调用poll(),然后处理由查找套接字返回的“事件”,并根据事件中的标志
# 采取行动。
while True:
# Wait for at least one of the sockets to be
# ready for processing.
print('waiting for the next event',file=sys.stderr)
events = poller.poll(TIMEOUT)
for fd,flag in events:
# Retrieve the actual socket from its file descriptor.
s = fd_to_socket[fd]
# 与select()类似,如果主服务器套接字是“可读的”,那么实际上这表示有来自客户的
# 一个连接。用READ_ONLY标志注册这个新连接,以便监视通过它的新数据。
# Handle inputs.
if flag & (select.POLLIN | select.POLLPRI):
if s is server:
# A readable socket is ready
# to accept a connection.
connection,client_address = s.accept()
print(' connection',client_address,
file=sys.stderr)
connection.setblocking(0)
fd_to_socket[connection,fileno()] = connection
poller.register(connection,READ_ONLY)
# Give the connection a queue for data to send.
message_queues[connection] = queue.Queue()
# 除了服务器以外,其他套接字都是现有的客户,可以使用recv()访问等待读取的数据。
else:
data = s.recv(1024)
# 如果recv()返回了数据,那么其会被放置在这个套接字相应的发出队列中。使用modify()
# 改变套接字的标志,使用poll()监视这个套接字是否准备好接收数据。
if data:
# A readable client socket has data.
print(' received {!r} from {}'.format(
data,s.getpeername()),file=sys.stderr,
)
message_queues[s].put(data)
# Add output channel for response.
poller.modify(s,READ_WRITE)
# recv()返回的空串表示客户已经断开连接,所以使用unregister()告诉poll对象忽略
# 这个套接字。
else:
# Interpret empty result as closed connection.
print(' closing',client_address,
file=sys.stderr)
# Stop listening for input on the connection.
poller.unregister(s)
s.close()
# Remove message queue.
del message_queues[s]
# POLLHUP标志指示一个客户“挂起”连接而没有将其妥善地关闭。服务器停止轮询消失的
# 客户。
elif flag & select.POLLHUP:
# Client hung up.
print(' closing',client_address,'(HUP)',
file=stderr)
# Stop listening for input on the connection.
poller.unregister(s)
s.close()
# 可写套接字的处理看起来与select()例子中使用的版本类似,但却使用了modify()来改变
# 轮询服务器中套接字的标志,而不是将它从输出列表中删除。
elif flag & select.POLLOUT:
# Socket is ready to send data,
# if there is any to send.
try:
next_msg = message_queues[s].get_nowait()
except queue.Empty:
# No messages waiting,so stop checking.
print(s.getpeername(),'queue empty',
file=sys.stderr)
poller.modify(s,READ_ONLY)
else:
print(' sending {!r} to {}'.format(
next_msg,s.getpeername()),file=sys.stderr,
)
s.send(next_msg)
# 最后一点,任何有POLLERR错误的事件都会导致服务器关闭套接字。
elif flag & select.POLLERR:
print(' exception on',s.getpeername(),
file=sys.stderr)
# Stop listening for input on the connection.
poller.unregister(s)
s.close()
# Remove message queue.
del message_queues[s]
基于轮询的服务器与select_echo_multiclient.py(使用多个套接字的客户程序)一起运行时,会生成以下输出。
11.4.4
select还提供了一些可移植性较差的选项,包括epoll(Linux支持的边界轮询API)、kqueue(使用了BSD的内核队列)和kevent(BSD的内核事件接口)。有关这些选项如何工作的更多详细内容请参考操作系统库文档。