7. IO 多路复用之 poll

这小节我们介绍 IO 多路复用技术的 poll。

7.1 poll 是什么

poll 是类似于 select 的又一个 linux 提供的内核函数,但是 poll 的底层实现和 select 相比更高效,更详细的差异后面的章节会讲到。

7.2 使用 poll

Python 中 poll 的实现同样也在 select module 中,但是不同于 select,poll 是通过主动注册文件描述符和事件实现对文件描述符的监听,而且 poll 提供了一些列的事件 flag 来进行对应的事件注册:

事件 含义
POLLIN There is data to read
POLLPRI There is urgent data to read
POLLOUT Ready for output: writing will not block
POLLERR Error condition of some sort
POLLHUP Hung up
POLLNVAL Invalid request: descriptor not open

同上文中 select 的例子一样,我们使用 poll 创建一个回显服务,区别是 poll 需要主动注册文件描述符和需要监听的事件:


READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
READ_WRITE = READ_ONLY | select.POLLOUT

# Set up the poller
poller = select.poll()
poller.register(server, READ_ONLY)

由于 poll 返回的是一个文件描述符号对应事件的列表,所以我们需要提前记住所有文件描述符的 fileno:

fd_to_socket = { server.fileno(): server}

poll 也有超时设置,不过 poll 的 timeout 单位是 ms,为了让 poll 不会一直阻塞,我们指定 timeout 为 1s:

TIMEOUT = 1000

开启主循环,在 timeout 之后检测返回的事件列表和对应的文件描述符:

while True:
    events = poller.poll(TIMEOUT)
    for fd, flag in events:
        pass

7.3 读事件 POLLIN 和 POLLPRI

poll 的读事件对应的是 POLLINPOLLPRI ,同 select 读事件一样也有以下的几种情形。

情形一

如果文件描述符是 server,说明有新的连接到来,在获取这个连接之后需要把新的连接通过 poll 来进行注册,这样我们就可以监听新连接的事件了:


if s is server:
    connection, client_address = s.accept()
    connection.setblocking(0)
    fd_to_socket[ connection.fileno() ] = connection
    poller.register(connection, READ_ONLY)

    message_queues[connection] = Queue.Queue()

情形二

如果文件描述符不是 server,说明这个文件描述符有数据可读,此时我们可以使用 recv 来读取数据:

else:
    data = s.recv(1024)
    if data:
        message_queues[s].put(data.capitalize())
        poller.modify(s, READ_WRITE)

与此同时,我们也需要修改这个文件描述符需要被监听的事件类型,在读完数据之后我们需要进行数据回显,因此修改事件类型为 READ_WRITE,这点与 select 不同。

情形三

如果读取的数据是空的,说明 client 关闭了这个连接,我们需要把这个连接从 poll 的注册列表中移除,并且释放对应的资源:

poller.unregister(s)
s.close()

# Remove message queue
del message_queues[s]

7.4 写事件 POLLOUT

对有写事件的文件描述符的处理和 select 类似,区别在于如果没有消息可写,我们修改描述符的监听事件为 READ_ONLY

elif flag & select.POLLOUT:
    try:
        next_msg = message_queues[s].get_nowait()
    except Queue.Empty:
        poller.modify(s, READ_ONLY)
    else:
        s.send(next_msg)

7.5 异常处理 POLLHUP 和 POLLERR

POLLHUP 说明 client 没有正确关闭连接,POLLERR 说明有错误发生,这两种情形我们选择取消对文件描述符的监听,并且释放资源:


poller.unregister(s)
s.close()

# Remove message queue
del message_queues[s]

7.6 poll 和 select 的对比

select 和 poll 都支持同时监听多个文件描述符,但是他们有着显著的区别。

select

  1. select 有最大文件描述符限制
  2. 为了获取具体哪个文件描述符有事件可用,select 需要对所有监听的文件描述符进行线性扫描
  3. 每次调用 select ,操作系统都需把文件描述符集合在内核和用户空间中相互拷贝

poll

  1. poll 没有最大文件描述符限制
  2. 为了获取哪个文件描述符,poll 也需要对监听的文件描述符进行线性扫描
© 三月沙 all right reserved,powered by GitbookUpdated at 2019-05-23 11:02:59

results matching ""

    No results matching ""