socketserver ——网络服务器框架

源代码: Lib/socketserver.py


这个 socketserver 模块简化了写入网络服务器的任务。

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

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

这将使用InternetTCP协议,该协议提供客户机和服务器之间的连续数据流。如果 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 .

这四类处理请求 synchronously ;每个请求必须在启动下一个请求之前完成。如果每个请求需要很长时间才能完成,这是不合适的,因为它需要大量的计算,或者因为它返回大量的数据,而客户机处理这些数据很慢。解决方案是创建一个单独的进程或线程来处理每个请求; ForkingMixInThreadingMixIn 混合类可用于支持异步行为。

创建服务器需要几个步骤。首先,必须通过子类化 BaseRequestHandler 类并重写其 handle() 方法;此方法将处理传入的请求。第二,必须实例化其中一个服务器类,将服务器地址和请求处理程序类传递给它。建议在 with 语句。然后调用 handle_request()serve_forever() 处理一个或多个请求的服务器对象的方法。最后,调用 server_close() 关闭Socket(除非使用 with 声明)。

从继承时 ThreadingMixIn 对于线程连接行为,应该显式声明希望线程在突然关闭时的行为。这个 ThreadingMixIn 类定义属性 daemon_threads ,指示服务器是否应等待线程终止。如果希望线程自动运行,则应显式设置标志;默认值为 False ,这意味着在创建所有线程之前,python不会退出 ThreadingMixIn 已经退出。

服务器类具有相同的外部方法和属性,不管它们使用什么网络协议。

服务器创建说明

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

+------------+
| 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 下面提到的复刻类只在支持 fork() .

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

socketserver.ThreadingMixIn.server_close() 等待所有非守护进程线程完成,除非 socketserver.ThreadingMixIn.block_on_close 属性为假。通过设置使用后台线程 ThreadingMixIn.daemon_threadsTrue 直到线程完成。

在 3.7 版更改: socketserver.ForkingMixIn.server_close()socketserver.ThreadingMixIn.server_close() 现在等待所有子进程和非后台线程完成。添加新的 socketserver.ForkingMixIn.block_on_close 类属性,用于选择3.7之前的行为。

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

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

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

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

另一方面,如果您正在构建一个HTTP服务器,其中所有数据都存储在外部(例如,在文件系统中),那么在处理一个请求时,同步类基本上将使服务“聋”——如果客户机接收其请求的所有数据速度较慢,则这可能会持续很长时间。这里线程或复刻服务器是合适的。

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

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

服务器对象

class socketserver.BaseServer(server_address, RequestHandlerClass)

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

fileno()

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

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() 循环以停止并等待直到停止。 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 用于与客户机通信的套接字对象以及客户机的地址。

handle_error(request, client_address)

如果 handle() A方法 RequestHandlerClass 实例引发异常。默认操作是将跟踪打印回标准错误并继续处理进一步的请求。

在 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 版更改: 支持 context manager 添加了协议。退出上下文管理器等同于调用 server_close() .

请求处理程序对象

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.rfileself.wfile 属性。这个 self.rfileself.wfile 可以分别读取或写入属性,以获取请求数据或将数据返回到客户机。

这个 rfile 两个类的属性都支持 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() 调用第二个处理程序将调用 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

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

异步混合

要构建异步处理程序,请使用 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 类的使用方式相同,只是服务器将为每个请求生成一个新进程。仅在支持的POSIX平台上可用 fork() .