socketserver
— 网络服务器框架
¶
源代码: Lib/socketserver.py
The
socketserver
模块简化编写网络服务器的任务。
有 4 个基本具体服务器类:
socketserver.
TCPServer
(
server_address
,
RequestHandlerClass
,
bind_and_activate=True
)
¶
这使用 Internet TCP 协议,在客户端和服务器之间提供连续数据流。若
bind_and_activate
为 True,构造函数自动试图援引
server_bind()
and
server_activate()
。其它参数被传递给
BaseServer
基类。
socketserver.
UDPServer
(
server_address
,
RequestHandlerClass
,
bind_and_activate=True
)
¶
这使用是离散信息数据包 (可能乱序到达或在传输过程中丢失) 的数据报。参数如同
TCPServer
.
socketserver.
UnixStreamServer
(
server_address
,
RequestHandlerClass
,
bind_and_activate=True
)
¶
socketserver.
UnixDatagramServer
(
server_address
,
RequestHandlerClass
,
bind_and_activate=True
)
¶
这些不常用类类似于 TCP 和 UDP 类,但使用 Unix 域套接字;它们不可用于非 Unix 平台。参数如同
TCPServer
.
这 4 个类处理请求
同步
;每个请求必须在下一个请求启动之前完成。这不合适若每个请求花很长时间才完成,因为它要求大量计算,或者因为它返回客户端处理缓慢的大量数据。解决方案是创建单独进程 (或线程) 以处理每个请求;
ForkingMixIn
and
ThreadingMixIn
混合类可以用于支持异步行为。
创建服务器要求几个步骤。首先,必须创建请求处理程序类通过子类化
BaseRequestHandler
类并覆写其
handle()
方法;此方法将处理传入请求。其次,必须实例化一个服务器类,将服务器地址和请求处理程序类传递给它。推荐使用服务器在
with
语句。然后调用
handle_request()
or
serve_forever()
方法在服务器对象以处理一个或多个请求。最后,调用
server_close()
以关闭套接字 (除非使用
with
语句)。
当继承自
ThreadingMixIn
为线程化连接行为,应明确声明想要线程在突然关闭时的行为如何。
ThreadingMixIn
类定义属性
daemon_threads
指示服务器是否应该等待线程终止。应明确设置标志,若愿意让线程行为自主;默认为
False
,意味着 Python 不会退出,直到创建所有线程通过
ThreadingMixIn
的退出。
服务器类拥有相同的外部方法和属性,无论它们使用什么网络协议。
在继承简图中有 5 个类,其中 4 个表示 4 种类型的同步服务器:
+------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
注意,
UnixDatagramServer
派生自
UDPServer
,不是来自
UnixStreamServer
— IP 和 Unix 流服务器之间的唯一差异是地址族,在两个 Unix 服务器类中简单重复。
socketserver.
ForkingMixIn
¶
socketserver.
ThreadingMixIn
¶
可以使用这些混合类为每种服务器类型创建分叉和线程化版本。例如,
ThreadingUDPServer
的创建如下:
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
混合类首先出现,由于它覆盖的方法定义在
UDPServer
。设置各种属性也会改变底层服务器机制的行为。
ForkingMixIn
和下文提及的分叉类只可用于 POSIX (便携式操作系统接口) 平台支持
fork()
.
socketserver.ForkingMixIn.server_close()
等待直到所有子级进程完成,除了若
socketserver.ForkingMixIn.block_on_close
属性为 False。
socketserver.ThreadingMixIn.server_close()
等待直到所有非守护程序线程完成,除了若
socketserver.ThreadingMixIn.block_on_close
属性为 False。使用守护线程通过设置
ThreadingMixIn.daemon_threads
to
True
不等待直到线程完成。
3.7 版改变:
socketserver.ForkingMixIn.server_close()
and
socketserver.ThreadingMixIn.server_close()
现在等待直到所有子级进程和非守护线程完成。添加新的
socketserver.ForkingMixIn.block_on_close
类属性以选择加入 3.7 之前的行为。
socketserver.
ForkingTCPServer
¶
socketserver.
ForkingUDPServer
¶
socketserver.
ThreadingTCPServer
¶
socketserver.
ThreadingUDPServer
¶
这些类是使用混合类预定义的。
要实现服务,必须派生类从
BaseRequestHandler
并重新定义其
handle()
方法。然后,可以运行服务的各种版本通过组合某一服务器类与请求处理程序类。请求处理程序类必须不同,对于数据报 (或流) 服务。可以隐藏这通过使用处理程序子类
StreamRequestHandler
or
DatagramRequestHandler
.
当然,仍必须动动脑筋!例如,使用分叉服务器没有意义若服务在内存中包含可以被不同请求修改的状态,由于子级进程中的修改从不会到达父级进程中保持的初始状态,并传递给各子级。在此情况下,可以使用线程服务器,但可能必须使用锁来保护共享数据的完整性。
另一方面,若正构建所有数据存储在外部 (例如,在文件系统中) 的 HTTP 服务器,同步类基本上会使服务 "置若罔闻" 当要处理某个请求时 – 可能持续很长时间,若客户端缓慢接收它所请求的所有数据。在这里,线程化 (或分叉) 服务器是合适的。
在某些情况下,同步处理请求部分可能是合适的,但要基于请求数据在分叉子级中完成处理。这可以通过使用同步服务器实现,并做明确分叉在请求处理程序类
handle()
方法。
同时处理多个请求的另一方式若环境既不支持线程也不支持
fork()
(太昂贵或不适合服务),是维护已部分完成请求的明确表格,和使用
selectors
决定接下来要处理哪个请求 (或是否要处理新的传入请求)。这对流服务尤其重要,当各客户端可以潜在连接很长时间 (若无法使用线程或子进程)。见
asyncore
了解管理这的另一方式。
socketserver.
BaseServer
(
server_address
,
RequestHandlerClass
)
¶
这是模块中所有服务器对象的超类。它定义下文给出的接口,但不实现大多数在子类中完成的方法。2 参数存放在各自的
server_address
and
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 版改变:
添加
service_actions
调用到
serve_forever
方法。
service_actions
(
)
¶
这被调用在
serve_forever()
循环。此方法可以通过子类化 (或混合类) 被覆盖,以履行特定于给定服务的动作,譬如:清理动作。
3.3 版新增。
shutdown
(
)
¶
告诉
serve_forever()
循环要停止并等待直到它完成。
shutdown()
必须被调用当
serve_forever()
运行在不同线程,否则会死锁。
server_close
(
)
¶
清理服务器。可能被覆盖。
address_family
¶
服务器套接字所属的协议系列。常见范例
socket.AF_INET
and
socket.AF_UNIX
.
RequestHandlerClass
¶
由用户提供的请求处理程序类;每个请求都会创建此类的实例。
server_address
¶
服务器正监听的具体地址。地址格式因协议系列不同而异;见文档编制为
socket
module for details. For Internet protocols, this is a tuple containing a string giving the address, and an integer port number:
('127.0.0.1', 80)
,例如。
socket
¶
服务器将监听传入请求的套接字对象。
服务器类支持下列类变量:
request_queue_size
¶
请求队列的大小。若处理单个请求花费很长时间,当服务器忙时到达的任何请求会被放入队列,直到到达
request_queue_size
要求。一旦队列已满,来自客户端的进一步请求将获得 "连接被拒" 错误。默认值通常为 5,但子类可以覆盖这。
socket_type
¶
由服务器使用的套接字类型;
socket.SOCK_STREAM
and
socket.SOCK_DGRAM
是 2 常见值。
timeout
¶
超时持续时间 (以秒为单位度量),或
None
若不期望超时。若
handle_request()
在超时周期内未收到传入请求,
handle_timeout()
方法被调用。
有各种服务器方法可以覆盖,被子类化的基服务器类像
TCPServer
;这些方法没用,对于服务器对象的外部用户。
finish_request
(
request
,
client_address
)
¶
实际处理请求通过实例化
RequestHandlerClass
并调用其
handle()
方法。
get_request
(
)
¶
必须接受来自套接字的请求,且返回的 2 元素元组包含 new 套接字对象用于与客户端通信,和客户端地址。
handle_error
(
request
,
client_address
)
¶
此函数被调用若
handle()
方法对于
RequestHandlerClass
实例引发异常。默认动作是将回溯打印到标准错误并继续处理进一步请求。
3.6 版改变:
现在仅被调用异常派生自
Exception
类。
handle_timeout
(
)
¶
此函数被调用当
timeout
属性有设置值除了
None
且超时周期已过去但没有收到请求。分叉服务器的默认动作是收集已退出的任何子级进程的状态,而在线程服务器中,此方法什么都不做。
process_request
(
request
,
client_address
)
¶
调用
finish_request()
以创建实例化的
RequestHandlerClass
。若期望,此函数可以创建新的进程 (或线程) 来处理请求;
ForkingMixIn
and
ThreadingMixIn
类会这样做。
server_bind
(
)
¶
被调用通过服务器的构造函数将套接字绑定到期望地址。可能被覆盖。
verify_request
(
request
,
client_address
)
¶
必须返回布尔值;若值为
True
,请求将被处理,和若它为
False
,请求将被拒绝。可以覆盖此函数以实现对服务器的访问控制。默认实现始终返回
True
.
3.6 版改变:
支持
上下文管理器
协议被添加。退出上下文管理器相当于调用
server_close()
.
socketserver.
BaseRequestHandler
¶
这是所有请求处理程序对象的超类。它定义了接口,下文给出。具体请求处理程序子类必须定义新的
handle()
方法,且可以覆盖任何其它方法。为每个请求创建新的子类实例。
handle
(
)
¶
此函数必须做服务请求要求的所有工作。默认实现什么都不做。它有几个可用实例属性;可用的请求如
self.request
;客户端地址如
self.client_address
;和服务器实例如
self.server
,若它需要访问每台服务器的信息。
类型对于
self.request
是不同的对于数据报 (或流服务)。对于流服务而言,
self.request
是套接字对象;对于数据报服务,
self.request
是字符串和套接字对。
socketserver.
StreamRequestHandler
¶
socketserver.
DatagramRequestHandler
¶
这些
BaseRequestHandler
子类覆盖
setup()
and
finish()
方法,和提供
self.rfile
and
self.wfile
属性。
self.rfile
and
self.wfile
属性可以分别读取或写入,以获取请求数据或将数据返回给客户端。
The
rfile
属性的 2 类支持
io.BufferedIOBase
可读接口,和
DatagramRequestHandler.wfile
支持
io.BufferedIOBase
可写接口。
3.6 版改变:
StreamRequestHandler.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("{} wrote:".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()
在第 2 个处理程序中的调用将调用
recv()
多次直到遇到换行符,而单个
recv()
在第 1 个处理程序中的调用将仅仅返回从客户端发送的内容在某一
sendall()
调用。
这是客户端侧:
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
and
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
The
ForkingMixIn
类使用方式相同,除服务器会为每个请求卵生新的进程外。只可用于 POSIX (便携式操作系统接口) 平台支持
fork()
.