# 21.21。 `socketserver` - 网络服务器框架

源代码： Lib / socketserver.py

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

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

class `socketserver.``TCPServer`(server_address, RequestHandlerClass, bind_and_activate=True)

  这使用Internet TCP协议，它在客户端和服务器之间提供连续的数据流。如果bind_and_activate为true，构造函数将自动尝试调用`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()`方法来创建请求处理程序类；此方法将处理传入请求。其次，您必须实例化一个服务器类，将它传递给服务器的地址和请求处理程序类。然后调用服务器对象的`handle_request()`或`serve_forever()`方法来处理一个或多个请求。最后，调用`server_close()`关闭套接字。

当从`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

  混合类是第一个，因为它覆盖了`UDPServer`中定义的方法。设置各种属性还会更改底层服务器机制的行为。

class `socketserver.``ForkingTCPServer`
class `socketserver.``ForkingUDPServer`
class `socketserver.``ThreadingTCPServer`
class `socketserver.``ThreadingUDPServer`

  这些类是使用mix-in类预定义的。

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

当然，你还是要用你的头！对于实例，如果服务包含可以由不同请求修改的内存中的状态，则使用forking服务器没有意义，因为子进程中的修改将永远不会达到父进程中保存的初始状态，并传递给每个子进程。在这种情况下，您可以使用线程服务器，但您可能必须使用锁来保护共享数据的完整性。

另一方面，如果您正在构建一个HTTP服务器，其中所有数据都存储在外部（对于实例，在文件系统中），同步类将基本上在处理一个请求时渲染服务“聋” - 这可能是如果客户端缓慢地接收其请求的所有数据，则很长时间。这里一个线程或分叉服务器是适当的。

在一些情况下，可能适合于同步地处理请求的一部分，但是取决于请求数据而在分叉的孩子中完成处理。这可以通过使用同步服务器并在请求处理程序类`handle()`中执行显式fork来实现。

在既不支持线程也不支持`fork()`（或者这些对于服务来说太贵或不适用的环境）中处理多个同时请求的另一种方法是维护部分完成的请求的显式表，使用`selectors`来决定下一个要处理的请求（或是否处理新的传入请求）。这对于每个客户端可能长时间连接（如果线程或子进程不能使用）的流服务特别重要。有关管理此操作的另一种方法，请参见`asyncore`。

## 21.21.2. 服务器对象

class `socketserver.``BaseServer`(server_address, RequestHandlerClass)

  这是模块中所有服务器对象的超类。它定义了接口，如下所示，但不实现大多数方法，这是在子类中完成的。这两个参数存储在相应的`server_address`和`RequestHandlerClass`属性中。

  `fileno`()

    返回整数文件描述服务器正在侦听的套接字的器件。此函数最常传递到`selectors`，以允许在同一进程中监视多个服务器。

  `handle_request`()

    处理单个请求。This function calls the following methods in order: `get_request()`, `verify_request()`, and `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中已更改：添加了对`serve_forever`方法的`service_actions`调用。

  `service_actions`()

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

    版本3.3中的新功能。

  `shutdown`()

    告诉`serve_forever()`循环停止并等待，直到它。

  `server_close`()

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

  `address_family`

    服务器的套接字所属的协议族。常见示例为`socket.AF_INET`和`socket.AF_UNIX`。

  `RequestHandlerClass`

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

  `server_address`

    服务器正在侦听的地址。地址的格式因协议族而异；有关详细信息，请参阅`socket`模块的文档。对于Internet协议，这是一个包含提供地址的字符串和整数端口号的元组：`（'127.0.0.1'， 80） t0 >。`

  `socket`

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

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

  `allow_reuse_address`

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

  `request_queue_size`

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

  `socket_type`

    服务器使用的套接字类型； `socket.SOCK_STREAM`和`socket.SOCK_DGRAM`是两个常见值。

  `timeout`

    超时持续时间（以秒为单位），或`None`（如果不需要超时）。如果`handle_request()`在超时期限内未收到传入请求，则会调用`handle_timeout()`方法。

  有各种服务器方法可以被基本服务器类的子类覆盖，例如`TCPServer`；这些方法对服务器对象的外部用户无用。

  `finish_request`()

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

  `get_request`()

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

  `handle_error`(request, client_address)

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

  `handle_timeout`()

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

  `process_request`(request, client_address)

    Calls `finish_request()` to create an instance of the `RequestHandlerClass`. 如果需要，此函数可以创建一个新的进程或线程来处理请求； `ForkingMixIn`和`ThreadingMixIn`类执行此操作。

  `server_activate`()

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

  `server_bind`()

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

  `verify_request`(request, client_address)

    必须返回布尔值；如果值为`True`，则会处理请求，如果它`False`，则请求将被拒绝。可以覆盖此功能以实现服务器的访问控制。默认实现总是返回`True`。

## 21.21.3. 请求处理程序对象

class `socketserver.``BaseRequestHandler`

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

  `setup`()

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

  `handle`()

    此函数必须执行服务请求所需的所有工作。默认实现什么也不做。几个实例属性可用于它；该请求可用作`self.request`；客户端地址为`self.client_address`；和服务器实例为`self.server`，以防需要访问每个服务器的信息。

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

  `finish`()

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

class `socketserver.``StreamRequestHandler`
class `socketserver.``DatagramRequestHandler`

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

## 21.21.4. Examples

### 21.21.4.1. `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
        server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

        # 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
        server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
        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. Asynchronous Mixins

要构建异步处理程序，请使用`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)
        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()
        server.server_close()

示例的输出应如下所示：

    $ 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`类以相同的方式使用，除了服务器将为每个请求生成一个新进程。