欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python3标准库:selectors I/O多路复用抽象

程序员文章站 2022-03-10 12:31:18
1. selectors I/O多路复用抽象 selectors模块在select中平台特定的I/O监视函数之上提供了一个平*立的抽象层。 1.1 操作模型 selectors中的API是基于事件的,与select中的poll()类似。它有多个实现,并且这个模块会自动设置别名DefaultSele ......

1. selectors i/o多路复用抽象

selectors模块在select中平台特定的i/o监视函数之上提供了一个平*立的抽象层。

1.1 操作模型

selectors中的api是基于事件的,与select中的poll()类似。它有多个实现,并且这个模块会自动设置别名defaultselector来指示对当前系统配置最为高效的一个实现。
选择器对象提供了一些方法,可以指定在一个套接字上查找哪些事件,然后以一种平*立的方式让调用者等待事件。注册对事件的兴趣会创建一个selectorkey,其中包含套接字、所注册事件的有关信息,可能还有可选的应用数据。选择器的所有者调用它的select()方法来了解事件。返回值是一个键对象序列和一个指示发生了哪些事件的位掩码。使用选择器的程序要反复调用select(),然后适当地处理事件。

1.2 回送服务器

这里给出的回送服务器例子使用了selectorkey中的应用数据来注册发生新事件时要调用的一个回调函数。主循环从键得到这个回调,并把套接字和事件掩码传递给该回调。服务器启动时,其会注册当主服务器套接字上发生读事件时要调用的accept()函数。接受连接会产生一个新的套接字,然后注册read()函数作为读事件的一个回调。

import selectors
import socket

mysel = selectors.defaultselector()
keep_running = true

def read(connection, mask):
    "callback for read events"
    global keep_running

    client_address = connection.getpeername()
    print('read({})'.format(client_address))
    data = connection.recv(1024)
    if data:
        # a readable client socket has data
        print('  received {!r}'.format(data))
        connection.sendall(data)
    else:
        # interpret empty result as closed connection
        print('  closing')
        mysel.unregister(connection)
        connection.close()
        # tell the main loop to stop
        keep_running = false

def accept(sock, mask):
    "callback for new connections"
    new_connection, addr = sock.accept()
    print('accept({})'.format(addr))
    new_connection.setblocking(false)
    mysel.register(new_connection, selectors.event_read, read)

server_address = ('localhost', 9999)
print('starting up on {} port {}'.format(*server_address))
server = socket.socket(socket.af_inet, socket.sock_stream)
server.setblocking(false)
server.bind(server_address)
server.listen(5)

mysel.register(server, selectors.event_read, accept)

while keep_running:
    print('waiting for i/o')
    for key, mask in mysel.select(timeout=1):
        callback = key.data
        callback(key.fileobj, mask)

print('shutting down')
mysel.close()

如果read()没有从套接字接收到任何数据,那么当连接的另一端关闭时,它会中断读事件而不是发送数据。之后,会从选择器删除这个套接字,并将其关闭。由于这只是一个示例程序,所以这个服务器与唯一的客户结束通信后还会关闭服务器自身。

1.3 回送客户

下面的回送客户例子会处理主循环中的所有i/o事件,而不是使用回调。它会建立选择器来报告套接字上的读事件,并报告套接字什么时候准备好可以发送数据。由于它查看两种类型的事件,所以客户必须通过查看掩码值来检查发生了哪个事件。所有数据都发出后,它会修改选择器配置,只在有可读取的数据时才会报告。

import selectors
import socket

mysel = selectors.defaultselector()
keep_running = true
outgoing = [
    b'it will be repeated.',
    b'this is the message.  ',
]
bytes_sent = 0
bytes_received = 0

# connecting is a blocking operation, so call setblocking()
# after it returns.
server_address = ('localhost', 9999)
print('connecting to {} port {}'.format(*server_address))
sock = socket.socket(socket.af_inet, socket.sock_stream)
sock.connect(server_address)
sock.setblocking(false)

# set up the selector to watch for when the socket is ready
# to send data as well as when there is data to read.
mysel.register(
    sock,
    selectors.event_read | selectors.event_write,
)

while keep_running:
    print('waiting for i/o')
    for key, mask in mysel.select(timeout=1):
        connection = key.fileobj
        client_address = connection.getpeername()
        print('client({})'.format(client_address))

        if mask & selectors.event_read:
            print('  ready to read')
            data = connection.recv(1024)
            if data:
                # a readable client socket has data
                print('  received {!r}'.format(data))
                bytes_received += len(data)

            # interpret empty result as closed connection,
            # and also close when we have received a copy
            # of all of the data sent.
            keep_running = not (
                data or
                (bytes_received and
                 (bytes_received == bytes_sent))
            )

        if mask & selectors.event_write:
            print('  ready to write')
            if not outgoing:
                # we are out of messages, so we no longer need to
                # write anything. change our registration to let
                # us keep reading responses from the server.
                print('  switching to read-only')
                mysel.modify(sock, selectors.event_read)
            else:
                # send the next message.
                next_msg = outgoing.pop()
                print('  sending {!r}'.format(next_msg))
                sock.sendall(next_msg)
                bytes_sent += len(next_msg)

print('shutting down')
mysel.unregister(connection)
connection.close()
mysel.close()

这个客户不仅跟踪它发出的数据量,还会跟踪接收的数据量。当这些值一致而且非0时,客户退出处理循环,并妥善地关闭,它将从选择器删除套接字,并关闭套接字和选择器。

1.4 服务器和客户

要在不同的终端窗口运行客户和服务器,使它们能够相互通信。服务器输出显示了入站连接和数据,以及发回给客户的响应。

Python3标准库:selectors I/O多路复用抽象

客户输入显示了发出的信息和从服务器得到的响应。

Python3标准库:selectors I/O多路复用抽象