21.21. socketserver — 网络服务器框架 — Python 文档

来自菜鸟教程
Python/docs/3.6/library/socketserver
跳转至:导航、​搜索

21.21. 套接字服务器 — 网络服务器框架

源代码: :source:`Lib/socketserver.py`



socketserver模块简化了编写网络服务器的任务。

有四个基本的具体服务器类:

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)
这使用 Internet 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相同。

这四个类同步处理请求'; 每个请求都必须完成,然后才能开始下一个请求。 如果每个请求需要很长时间才能完成,这不合适,因为它需要大量计算,或者因为它返回大量客户端处理缓慢的数据。 解决方法是创建一个单独的进程或线程来处理每个请求; ForkingMixInThreadingMixIn 混合类可用于支持异步行为。

创建服务器需要几个步骤。 首先,您必须通过继承 BaseRequestHandler 类并覆盖其 handle() 方法来创建请求处理程序类; 此方法将处理传入的请求。 其次,您必须实例化其中一个服务器类,将服务器地址和请求处理程序类传递给它。 建议在 with 语句中使用服务器。 然后调用服务器对象的handle_request()serve_forever()方法来处理一个或多个请求。 最后,调用 server_close() 关闭套接字(除非您使用了 with 语句)。

当从 ThreadingMixIn 继承线程连接行为时,您应该明确声明您希望线程在突然关闭时的行为方式。 ThreadingMixIn 类定义了一个属性 daemon_threads,它指示服务器是否应该等待线程终止。 如果您希望线程自主运行,您应该显式设置该标志; 默认值为 False,这意味着 Python 不会退出,直到 ThreadingMixIn 创建的所有线程都退出。

无论使用何种网络协议,服务器类都具有相同的外部方法和属性。

21.21.1。 服务器创建说明

一个继承图中有五个类,其中四个代表四种类型的同步服务器:

+------------+
| BaseServer |
+------------+
      |
      v
+-----------+        +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+        +------------------+
      |
      v
+-----------+        +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+        +--------------------+

请注意,UnixDatagramServer 来自 UDPServer,而不是来自 UnixStreamServer——IP 和 Unix 流服务器之间的唯一区别是地址族,它在两个 Unix 服务器类。

class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn

可以使用这些混合类创建每种类型服务器的分叉和线程版本。 例如,ThreadingUDPServer 的创建如下:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

mix-in 类首先出现,因为它覆盖了 UDPServer 中定义的方法。 设置各种属性也会改变底层服务器机制的行为。

ForkingMixIn 和下面提到的 Forking 类仅在支持 fork() 的 POSIX 平台上可用。

class socketserver.ForkingTCPServer

class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer

这些类是使用混合类预定义的。

要实现服务,您必须从 BaseRequestHandler 派生一个类并重新定义其 handle() 方法。 然后,您可以通过将服务器类之一与您的请求处理程序类组合来运行服务的各种版本。 数据报或流服务的请求处理程序类必须不同。 这可以通过使用处理程序子类 StreamRequestHandlerDatagramRequestHandler 来隐藏。

当然,你还是得用你的脑袋! 例如,如果服务在内存中包含可由不同请求修改的状态,那么使用分叉服务器是没有意义的,因为子进程中的修改永远不会达到父进程中保存的初始状态并传递给每个子进程. 在这种情况下,您可以使用线程服务器,但您可能必须使用锁来保护共享数据的完整性。

另一方面,如果您正在构建一个 HTTP 服务器,其中所有数据都存储在外部(例如,在文件系统中),则同步类本质上将在处理一个请求时将服务呈现为“聋”——这可能是为了如果客户端接收它请求的所有数据很慢,则需要很长时间。 这里适合使用线程或分叉服务器。

在某些情况下,同步处理请求的一部分可能是合适的,但根据请求数据在分叉的子进程中完成处理。 这可以通过使用同步服务器并在请求处理程序类 handle() 方法中进行显式分叉来实现。

在既不支持线程也不支持 fork()(或者这些对于服务来说太昂贵或不合适的地方)的环境中处理多个同时请求的另一种方法是维护一个部分完成的请求的显式表并使用选择器决定接下来要处理哪个请求(或是否处理新的传入请求)。 这对于流服务尤其重要,其中每个客户端都可能连接很长时间(如果无法使用线程或子进程)。 请参阅 asyncore 以了解另一种管理方式。


21.21.2. 服务器对象

class socketserver.BaseServer(server_address, RequestHandlerClass)

这是模块中所有服务器对象的超类。 它定义了下面给出的接口,但不实现大多数方法,这是在子类中完成的。 这两个参数分别存储在 server_addressRequestHandlerClass 属性中。

fileno()

返回服务器正在侦听的套接字的整数文件描述符。 此功能最常传递给 选择器 ,以允许在同一进程中监视多个服务器。

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(),子类或 mixin 可以使用它来提供特定于给定服务的操作。 例如,ForkingMixIn 类使用 service_actions() 来清理僵尸子进程。

3.3 版更改: 添加 service_actions 调用 serve_forever 方法。

service_actions()

这在 serve_forever() 循环中调用。 此方法可以被子类或混合类覆盖,以执行特定于给定服务的操作,例如清理操作。

3.3 版中的新功能。

shutdown()

告诉 serve_forever() 循环停止并等待它完成。

server_close()

清理服务器。 可能会被覆盖。

address_family

服务器套接字所属的协议族。 常见的例子是 socket.AF_INETsocket.AF_UNIX

RequestHandlerClass

用户提供的请求处理程序类; 为每个请求创建此类的一个实例。

server_address

服务器正在侦听的地址。 地址的格式因协议族而异; 有关详细信息,请参阅 socket 模块的文档。 对于 Internet 协议,这是一个包含给出地址字符串和整数端口号的元组:例如,('127.0.0.1', 80)

socket

服务器将在其上侦听传入请求的套接字对象。

服务器类支持以下类变量:

allow_reuse_address

服务器是否允许重用地址。 这默认为 False,并且可以在子类中设置以更改策略。

request_queue_size

请求队列的大小。 如果处理单个请求需要很长时间,则任何在服务器繁忙时到达的请求都会放入队列,最多为 request_queue_size 个请求。 一旦队列已满,来自客户端的进一步请求将收到“连接被拒绝”错误。 默认值通常是 5,但这可以被子类覆盖。

socket_type

服务器使用的套接字类型; socket.SOCK_STREAMsocket.SOCK_DGRAM 是两个常见的值。

timeout

超时持续时间,以秒为单位,如果不需要超时,则为 None。 如果 handle_request() 在超时时间内没有收到传入请求,则调用 handle_timeout() 方法。

有多种服务器方法可以被基本服务器类的子类覆盖,如 TCPServer; 这些方法对服务器对象的外部用户没有用。

finish_request(request, client_address)

实际上通过实例化 RequestHandlerClass 并调用其 handle() 方法来处理请求。

get_request()

必须接受来自套接字的请求,并返回一个包含用于与客户端通信的 new 套接字对象和客户端地址的 2 元组。

handle_error(request, client_address)

如果 RequestHandlerClass 实例的 handle() 方法引发异常,则调用此函数。 默认操作是将回溯打印到标准错误并继续处理进一步的请求。

3.6 版更改: 现在只调用从 Exception 类派生的异常。

handle_timeout()

timeout 属性已设置为 None 以外的值并且超时期限已过且未收到任何请求时,将调用此函数。 分叉服务器的默认操作是收集已退出的任何子进程的状态,而在线程服务器中,此方法不执行任何操作。

process_request(request, client_address)

调用 finish_request() 以创建 RequestHandlerClass 的实例。 如果需要,这个函数可以创建一个新的进程或线程来处理请求; ForkingMixInThreadingMixIn 类执行此操作。

server_activate()

由服务器的构造函数调用以激活服务器。 TCP 服务器的默认行为只是在服务器的套接字上调用 listen()。 可能会被覆盖。

server_bind()

由服务器的构造函数调用以将套接字绑定到所需的地址。 可能会被覆盖。

verify_request(request, client_address)

必须返回一个布尔值; 如果值为True,请求将被处理,如果值为False,请求将被拒绝。 可以重写此函数以实现对服务器的访问控制。 默认实现总是返回 True

3.6 版更改: 添加了对 上下文管理器 协议的支持。 退出上下文管理器相当于调用 server_close()


21.21.3. 请求处理程序对象

class socketserver.BaseRequestHandler

这是所有请求处理程序对象的超类。 它定义了接口,如下所示。 具体的请求处理程序子类必须定义一个新的 handle() 方法,并且可以覆盖任何其他方法。 为每个请求创建一个新的子类实例。

setup()

handle() 方法之前调用以执行所需的任何初始化操作。 默认实现什么都不做。

handle()

此功能必须完成为请求提供服务所需的所有工作。 默认实现什么都不做。 它可以使用几个实例属性; 该请求可作为 self.request 使用; 客户端地址为self.client_address; 和服务器实例为 self.server,以防它需要访问每个服务器的信息。

self.request 的类型对于数据报或流服务是不同的。 对于流服务,self.request是一个socket对象; 对于数据报服务,self.request 是一对字符串和套接字。

finish()

handle() 方法之后调用以执行所需的任何清理操作。 默认实现什么都不做。 如果 setup() 引发异常,则不会调用此函数。

class socketserver.StreamRequestHandler
class socketserver.DatagramRequestHandler

这些 BaseRequestHandler 子类覆盖 setup()finish() 方法,并提供 self.rfileself.wfile 属性。 self.rfileself.wfile 属性可以分别读取或写入,以获取请求数据或返回数据给客户端。

两个类的rfile属性都支持io.BufferedIOBase可读接口,DatagramRequestHandler.wfile支持io.BufferedIOBase可写接口。

3.6 版本变更: StreamRequestHandler.wfile 也支持 io.BufferedIOBase 可写接口。


21.21.4. 例子

21.21.4.1。 套接字服务器.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() 调用会多次调用 recv() 直到遇到换行符,而第一个处理程序中的单个 recv() 调用只会返回在一次 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

21.21.4.2. 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 服务器示例完全相同。


21.21.4.3。 异步混合

要构建异步处理程序,请使用 ThreadingMixInForkingMixIn 类。

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 平台上可用。