第十一章:网络通信-select:高效等待I/O-使用poll()

博客介绍了poll()函数,它特性与select()类似但底层实现更有效,不过在windows不支持,程序可移植性较差。还提到利用poll()建立回送服务器的情况,以及select提供的一些可移植性较差的选项,如epoll、kqueue和kevent等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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的内核事件接口)。有关这些选项如何工作的更多详细内容请参考操作系统库文档。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值