socketserver
— 网络服务器框架¶
源代码: Lib/socketserver.py
socketserver
模块简化了编写网络服务器的任务。
可用性:不支持 Emscripten,不支持 WASI。
此模块在 WebAssembly 平台 wasm32-emscripten
和 wasm32-wasi
上不可用或无法工作。有关更多信息,请参阅 WebAssembly 平台。
有四个基本的具体服务器类
- class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
这使用互联网 TCP 协议,该协议提供客户端和服务器之间连续的数据流。如果 bind_and_activate 为真,则构造函数会自动尝试调用
server_bind()
和server_activate()
。其他参数将传递给BaseServer
基类。
- class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
这使用数据报,数据报是离散的信息包,在传输过程中可能会乱序到达或丢失。参数与
TCPServer
相同。
- class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
- class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)¶
这些不常用的类与 TCP 和 UDP 类类似,但使用 Unix 域套接字;它们在非 Unix 平台上不可用。参数与
TCPServer
相同。
这四个类以*同步*方式处理请求;每个请求必须在下一个请求开始之前完成。如果每个请求需要很长时间才能完成,则这不合适,因为它需要大量的计算,或者因为它返回了大量数据,而客户端处理速度很慢。解决方案是为每个请求创建一个单独的进程或线程来处理;ForkingMixIn
和 ThreadingMixIn
混入类可用于支持异步行为。
创建服务器需要几个步骤。首先,您必须通过子类化 BaseRequestHandler
类并覆盖其 handle()
方法来创建请求处理程序类;此方法将处理传入的请求。其次,您必须实例化一个服务器类,并将其传递给服务器的地址和请求处理程序类。建议在 with
语句中使用服务器。然后调用服务器对象的 handle_request()
或 serve_forever()
方法来处理一个或多个请求。最后,调用 server_close()
关闭套接字(除非您使用了 with
语句)。
当从 ThreadingMixIn
继承以实现线程连接行为时,您应该明确声明希望线程在突然关闭时的行为方式。ThreadingMixIn
类定义了一个属性 daemon_threads,该属性指示服务器是否应等待线程终止。如果您希望线程自主运行,则应显式设置该标志;默认值为 False
,这意味着在 ThreadingMixIn
创建的所有线程都退出之前,Python 不会退出。
服务器类具有相同的外部方法和属性,无论它们使用什么网络协议。
服务器创建说明¶
继承图中有五个类,其中四个表示四种类型的同步服务器
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
请注意,UnixDatagramServer
派生自 UDPServer
,而不是 UnixStreamServer
— IP 服务器和 Unix 服务器之间的唯一区别是地址族。
- class socketserver.ForkingMixIn¶
- class socketserver.ThreadingMixIn¶
可以使用这些混入类创建每种服务器类型的分叉和线程版本。例如,
ThreadingUDPServer
的创建方式如下class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
混入类排在首位,因为它会覆盖
UDPServer
中定义的方法。设置各种属性也会改变底层服务器机制的行为。ForkingMixIn
和下面提到的 Forking 类仅在支持fork()
的 POSIX 平台上可用。- block_on_close¶
如果
block_on_close
属性为False
,则ForkingMixIn.server_close
会等待所有子进程完成,否则会直接关闭。如果
block_on_close
属性为False
,则ThreadingMixIn.server_close
会等待所有非守护线程完成,否则会直接关闭。
- daemon_threads¶
对于
ThreadingMixIn
,通过将ThreadingMixIn.daemon_threads
设置为True
来使用守护线程,以避免等待线程完成。
在 3.7 版更改:
ForkingMixIn.server_close
和ThreadingMixIn.server_close
现在会等待所有子进程和非守护线程完成。添加一个新的ForkingMixIn.block_on_close
类属性以选择加入 3.7 之前的行为。
- class socketserver.ForkingTCPServer¶
- class socketserver.ForkingUDPServer¶
- class socketserver.ThreadingTCPServer¶
- class socketserver.ThreadingUDPServer¶
- class socketserver.ForkingUnixStreamServer¶
- class socketserver.ForkingUnixDatagramServer¶
- class socketserver.ThreadingUnixStreamServer¶
- class socketserver.ThreadingUnixDatagramServer¶
这些类是使用混入类预定义的。
3.12 版新增: 添加了 ForkingUnixStreamServer
和 ForkingUnixDatagramServer
类。
要实现服务,您必须从 BaseRequestHandler
派生一个类,并重新定义其 handle()
方法。然后,您可以通过将其中一个服务器类与您的请求处理程序类组合来运行服务的各种版本。数据报或流服务的请求处理程序类必须不同。这可以通过使用处理程序子类 StreamRequestHandler
或 DatagramRequestHandler
来隐藏。
当然,你还是要动动脑筋!例如,如果服务在内存中包含可以由不同请求修改的状态,则使用分叉服务器是没有意义的,因为子进程中的修改永远不会到达父进程中保留并传递给每个子进程的初始状态。在这种情况下,您可以使用线程服务器,但您可能必须使用锁来保护共享数据的完整性。
另一方面,如果您正在构建一个 HTTP 服务器,其中所有数据都存储在外部(例如,在文件系统中),那么同步类在处理一个请求时基本上会使服务“失聪”——如果客户端接收其请求的所有数据速度很慢,这可能会持续很长时间。这里适合使用线程或分叉服务器。
在某些情况下,可能适合同步处理部分请求,但根据请求数据在分叉的子进程中完成处理。这可以通过使用同步服务器并在请求处理程序类 handle()
方法中执行显式分叉来实现。
在不支持线程或 fork()
的环境中(或者在这些环境中对于服务来说过于昂贵或不合适),处理多个并发请求的另一种方法是维护一个部分完成请求的显式表,并使用 selectors
来决定接下来要处理哪个请求(或者是否要处理新的传入请求)。这对于每个客户端都可能长时间连接的流服务(如果不能使用线程或子进程)尤其重要。
服务器对象¶
- class socketserver.BaseServer(server_address, RequestHandlerClass)¶
这是模块中所有服务器对象的超类。它定义了下面给出的接口,但没有实现大多数方法,这些方法是在子类中实现的。这两个参数分别存储在
server_address
和RequestHandlerClass
属性中。- handle_request()¶
处理单个请求。此函数按顺序调用以下方法:
get_request()
、verify_request()
和process_request()
。如果处理程序类的用户提供handle()
方法引发异常,则将调用服务器的handle_error()
方法。如果在timeout
秒内未收到请求,则将调用handle_timeout()
并返回handle_request()
。
- serve_forever(poll_interval=0.5)¶
处理请求,直到发出显式
shutdown()
请求。每隔 poll_interval 秒轮询一次关闭请求。忽略timeout
属性。它还会调用service_actions()
,子类或混音可以使用它来提供特定于给定服务的动作。例如,ForkingMixIn
类使用service_actions()
清理僵尸子进程。在 3.3 版更改: 在
serve_forever
方法中添加了service_actions
调用。
- service_actions()¶
这在
serve_forever()
循环中调用。子类或混音类可以覆盖此方法,以执行特定于给定服务的动作,例如清理动作。3.3 版新增。
- shutdown()¶
告诉
serve_forever()
循环停止并等待它停止。shutdown()
必须在serve_forever()
在另一个线程中运行时调用,否则它将死锁。
- server_close()¶
清理服务器。可以被覆盖。
- address_family¶
服务器套接字所属的协议系列。常见的例子是
socket.AF_INET
和socket.AF_UNIX
。
- RequestHandlerClass¶
用户提供的请求处理程序类;将为每个请求创建一个此类的实例。
- server_address¶
服务器正在侦听的地址。地址的格式因协议系列而异;有关详细信息,请参阅
socket
模块的文档。对于互联网协议,这是一个包含字符串的元组,给出地址和一个整数端口号:例如('127.0.0.1', 80)
。
- socket¶
服务器将在其上侦听传入请求的套接字对象。
服务器类支持以下类变量
- request_queue_size¶
请求队列的大小。如果处理单个请求需要很长时间,则在服务器繁忙时到达的任何请求都将放入队列中,最多
request_queue_size
个请求。一旦队列已满,来自客户端的进一步请求将收到“连接被拒绝”错误。默认值通常为 5,但这可以被子类覆盖。
- socket_type¶
服务器使用的套接字类型;
socket.SOCK_STREAM
和socket.SOCK_DGRAM
是两个常见值。
- timeout¶
超时持续时间,以秒为单位,如果不需要超时,则为
None
。如果handle_request()
在超时时间内未收到传入请求,则调用handle_timeout()
方法。
基本服务器类(如
TCPServer
)的子类可以覆盖各种服务器方法;这些方法对服务器对象的外部用户没有用。- finish_request(request, client_address)¶
通过实例化
RequestHandlerClass
并调用其handle()
方法来实际处理请求。
- get_request()¶
必须接受来自套接字的请求,并返回一个 2 元组,其中包含用于与客户端通信的*新*套接字对象和客户端的地址。
- handle_error(request, client_address)¶
如果
RequestHandlerClass
实例的handle()
方法引发异常,则会调用此函数。默认操作是将回溯打印到标准错误并继续处理后续请求。版本 3.6 中的变化: 现在仅针对从
Exception
类派生的异常调用。
- handle_timeout()¶
当
timeout
属性已设置为None
以外的值并且超时时间已过但未收到任何请求时,将调用此函数。对于 forking 服务器,默认操作是收集已退出的任何子进程的状态,而在 threading 服务器中,此方法不执行任何操作。
- process_request(request, client_address)¶
调用
finish_request()
以创建RequestHandlerClass
的实例。如果需要,此函数可以创建一个新进程或线程来处理请求;ForkingMixIn
和ThreadingMixIn
类就是这样做。
- server_bind()¶
由服务器的构造函数调用以将套接字绑定到所需的地址。可以被覆盖。
- verify_request(request, client_address)¶
必须返回一个布尔值;如果值为
True
,则将处理该请求,如果值为False
,则将拒绝该请求。可以覆盖此函数以实现服务器的访问控制。默认实现始终返回True
。
版本 3.6 中的变化: 添加了对 上下文管理器 协议的支持。退出上下文管理器等效于调用
server_close()
。
请求处理程序对象¶
- class socketserver.BaseRequestHandler¶
这是所有请求处理程序对象的超类。它定义了如下所示的接口。具体的请求处理程序子类必须定义一个新的
handle()
方法,并且可以覆盖任何其他方法。为每个请求创建一个新的子类实例。- handle()¶
此函数必须完成服务请求所需的所有工作。默认实现不执行任何操作。它可以使用几个实例属性;请求作为
request
提供;客户端地址作为client_address
提供;服务器实例作为server
提供,以防它需要访问每个服务器的信息。对于数据报或流服务,
request
的类型不同。对于流服务,request
是一个套接字对象;对于数据报服务,request
是一对字符串和套接字。
- request¶
用于与客户端通信的*新*
socket.socket
对象。
- client_address¶
BaseServer.get_request()
返回的客户端地址。
- server¶
用于处理请求的
BaseServer
对象。
- class socketserver.StreamRequestHandler¶
- class socketserver.DatagramRequestHandler¶
这些
BaseRequestHandler
子类重写了setup()
和finish()
方法,并提供了rfile
和wfile
属性。- rfile¶
一个文件对象,用于读取接收到的请求。 支持
io.BufferedIOBase
可读接口。
- wfile¶
一个文件对象,用于写入回复。 支持
io.BufferedIOBase
可写接口。
版本 3.6 中的变化:
wfile
也支持io.BufferedIOBase
可写接口。
示例¶
socketserver.TCPServer
示例¶
这是服务器端
import socketserver
class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.
It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""
def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("Received from {}:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()
一个可选的请求处理程序类,它使用流(通过提供标准文件接口来简化通信的类文件对象)
class MyTCPHandler(socketserver.StreamRequestHandler):
def handle(self):
# self.rfile is a file-like object created by the handler;
# we can now use e.g. readline() instead of raw recv() calls
self.data = self.rfile.readline().strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# Likewise, self.wfile is a file-like object used to write back
# to the client
self.wfile.write(self.data.upper())
区别在于,第二个处理程序中的 readline()
调用将多次调用 recv()
直到遇到换行符,而第一个处理程序中的单个 recv()
调用将只返回到目前为止从客户端的 sendall()
调用接收到的内容(通常是所有内容,但这并不能由 TCP 协议保证)。
这是客户端
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
该示例的输出应如下所示
服务器
$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'
客户端
$ python TCPClient.py hello world with TCP
Sent: hello world with TCP
Received: HELLO WORLD WITH TCP
$ python TCPClient.py python is nice
Sent: python is nice
Received: PYTHON IS NICE
socketserver.UDPServer
示例¶
这是服务器端
import socketserver
class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""
def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
print(data)
socket.sendto(data.upper(), self.client_address)
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()
这是客户端
import socket
import sys
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
该示例的输出应该与 TCP 服务器示例完全相同。
异步混入类¶
要构建异步处理程序,请使用 ThreadingMixIn
和 ForkingMixIn
类。
ThreadingMixIn
类的示例
import socket
import threading
import socketserver
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
data = str(self.request.recv(1024), 'ascii')
cur_thread = threading.current_thread()
response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
self.request.sendall(response)
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def client(ip, port, message):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((ip, port))
sock.sendall(bytes(message, 'ascii'))
response = str(sock.recv(1024), 'ascii')
print("Received: {}".format(response))
if __name__ == "__main__":
# Port 0 means to select an arbitrary unused port
HOST, PORT = "localhost", 0
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
with server:
ip, port = server.server_address
# Start a thread with the server -- that thread will then start one
# more thread for each request
server_thread = threading.Thread(target=server.serve_forever)
# Exit the server thread when the main thread terminates
server_thread.daemon = True
server_thread.start()
print("Server loop running in thread:", server_thread.name)
client(ip, port, "Hello World 1")
client(ip, port, "Hello World 2")
client(ip, port, "Hello World 3")
server.shutdown()
该示例的输出应如下所示
$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3
ForkingMixIn
类的使用方法相同,只是服务器将为每个请求生成一个新进程。 仅在支持 fork()
的 POSIX 平台上可用。