socketserver —Web Service 器的框架

源代码: Lib/socketserver.py


socketserver模块简化了编写 Web Service 器的任务。

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

    • class * socketserver. TCPServer(* server_address RequestHandlerClass bind_and_activate = True *)
    • 这使用 Internet TCP 协议,该协议在 Client 端和服务器之间提供连续的数据流。如果* bind_and_activate *为 true,则构造函数将自动try调用server_bind()server_activate()。其他参数将传递给BaseServerBase Class。
    • 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相同。

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

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

当从ThreadingMixIn继承线程连接行为时,应明确语句希望线程在突然关闭时的行为。 ThreadingMixIn类定义属性* daemon_threads *,该属性指示服务器是否应 await 线程终止。如果希望线程自主运行,则应显式设置该标志。默认值为False,这意味着在ThreadingMixIn创建的所有线程都退出之前,Python 不会退出。

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

服务器创建说明

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

+------------+
| 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和下面提到的 Forking 类仅在支持fork()的 POSIX 平台上可用。

socketserver.ForkingMixIn.server_close()await 所有子进程完成,除非socketserver.ForkingMixIn.block_on_close属性为 false。

socketserver.ThreadingMixIn.server_close()await 直到所有非守护程序线程完成,除非socketserver.ThreadingMixIn.block_on_close属性为 false。pass将ThreadingMixIn.daemon_threads设置为True来使用守护线程,以免 await 线程完成。

在 3.7 版中进行了更改:socketserver.ForkingMixIn.server_close()socketserver.ThreadingMixIn.server_close()现在 await 所有子进程和非守护线程完成。在 3.7 版之前的行为中加入新的socketserver.ForkingMixIn.block_on_close类属性以选择加入。

  • 类别 socketserver. ForkingTCPServer
  • 类别 socketserver. ForkingUDPServer
  • 类别 socketserver. ThreadingTCPServer
  • 类别 socketserver. ThreadingUDPServer
    • 这些类是使用混合类预先定义的。

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

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

另一方面,如果您要构建一个 HTTP 服务器,其中所有数据都存储在外部(例如,在文件系统中),则同步类实际上将在处理一个请求时将服务“聋”起来–这可能是为了如果 Client 端接收请求的所有数据的速度很慢,则需要很长时间。在这里,线程服务器或分支服务器是合适的。

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

在既不支持线程又不支持fork()的环境中(或这些线程对于服务而言过于昂贵或不合适的环境)中处理多个同时请求的另一种方法是维护部分完成的请求的显式表,并使用selectors来决定处理哪个请求下一步(或是否处理新的传入请求)。这对于流服务非常重要,在流服务中,每个 Client 端都可能长时间连接(如果无法使用线程或子进程)。另请参见asyncore来进行 Management。

Server Objects

    • class * socketserver. BaseServer(* server_address RequestHandlerClass *)
    • 这是模块中所有 Server 对象的超类。它定义了下面给出的接口,但是没有实现大多数方法,这是在子类中完成的。这两个参数分别存储在server_addressRequestHandlerClass属性中。
  • fileno ( )

    • 返回服务器正在侦听的套接字的整数文件 Descriptors。此Function通常传递给selectors,以允许在同一进程中监视多个服务器。
  • handle_request ( )

  • serve_forever(* poll_interval = 0.5 *)

在版本 3.3 中进行了更改:向serve_forever方法添加了service_actions调用。

  • service_actions ( )
    • 称为serve_forever()循环。子类或混合类可以重写此方法,以执行特定于给定服务的操作,例如清除操作。

版本 3.3 中的新Function。

  • shutdown ( )

  • server_close ( )

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

  • RequestHandlerClass

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

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

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

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

  • allow_reuse_address

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

    • 请求队列的大小。如果处理单个请求需要很长时间,则将服务器繁忙时到达的所有请求放入队列,最多request_queue_size个请求。一旦队列已满,来自 Client 端的其他请求将收到“拒绝连接”错误。默认值通常为 5,但是可以被子类覆盖。
  • socket_type

  • timeout

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

  • finish_request((* request client_address *)

  • get_request ( )

    • 必须接受来自套接字的请求,并返回一个 2Tuples,其中包含用于与 Client 端通信的* new *套接字对象以及 Client 端的地址。
  • handle_error((* request client_address *)

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

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

  • handle_timeout ( )

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

  • server_activate ( )

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

    • 由服务器的构造函数调用,以将套接字绑定到所需的地址。可能会被覆盖。
  • verify_request((* request client_address *)

    • 必须返回一个布尔值;如果值为True,则将处理请求;如果值为False,则将拒绝请求。可以重写此Function以实现服务器的访问控制。默认实现始终返回True

在版本 3.6 中更改:添加了对context manager协议的支持。退出上下文 Management 器等效于调用server_close()

请求处理程序对象

  • 类别 socketserver. BaseRequestHandler

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

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

    • 此Function必须完成服务请求所需的所有工作。默认实现不执行任何操作。它可以使用几个实例属性。该请求可作为self.request使用;Client 地址为self.client_address;如果需要访问每个服务器的信息,则服务器实例为self.server

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

  • finish ( )

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

  • 类别 socketserver. DatagramRequestHandler

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

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

在 3.6 版中进行了更改:StreamRequestHandler.wfile还支持io.BufferedIOBase可写接口。

Examples

socketserver.TCPServer Example

这是服务器端:

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()

使用流的替代请求处理程序类(类似于文件的对象,pass提供标准文件接口来简化通信):

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()调用中从 Client 端发送的内容。

这是 Client 端:

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))

该示例的输出应如下所示:

Server:

$ python TCPServer.py
127.0.0.1 wrote:
b'hello world with TCP'
127.0.0.1 wrote:
b'python is nice'

Client:

$ 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 Example

这是服务器端:

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()

这是 Client 端:

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

Asynchronous Mixins

要构建异步处理程序,请使用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 平台上可用。