转载自品略图书馆 http://www.pinlue.com/article/2020/05/2413/5910601247206.html
1. selectors I/O多路复用抽象
selectors模块在select中平台特定的I/O监视函数之上提供了一个平台独立的抽象层。
1.1 操作模型
selectors中的API是基于事件的,与select中的poll()类似。它有多个实现,并且这个模块会自动设置别名DefaultSelector来指示对当前系统配置最为高效的一个实现。
选择器对象提供了一些方法,可以指定在一个套接字上查找哪些事件,然后以一种平台独立的方式让调用者等待事件。注册对事件的兴趣会创建一个SelectorKey,其中包含套接字、所注册事件的有关信息,可能还有可选的应用数据。选择器的所有者调用它的select()方法来了解事件。返回值是一个键对象序列和一个指示发生了哪些事件的位掩码。使用选择器的程序要反复调用select(),然后适当地处理事件。
1.2 回送服务器
这里给出的回送服务器例子使用了Selectorkey中的应用数据来注册发生新事件时要调用的一个回调函数。主循环从键得到这个回调,并把套接字和事件掩码传递给该回调。服务器启动时,其会注册当主服务器套接字上发生读事件时要调用的accept()函数。接受连接会产生一个新的套接字,然后注册read()函数作为读事件的一个回调。
import selectorsimport socketmysel = selectors.DefaultSelector()keep_running = Truedef 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 = Falsedef 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 selectorsimport socketmysel = selectors.DefaultSelector()keep_running = Trueoutgoing = [ b"It will be repeated.", b"This is the message. ",]bytes_sent = 0bytes_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 服务器和客户
要在不同的终端窗口运行客户和服务器,使它们能够相互通信。服务器输出显示了入站连接和数据,以及发回给客户的响应。
客户输入显示了发出的信息和从服务器得到的响应。