20.17. SocketServer —Web Service 器的框架

Note

SocketServer模块在 Python 3 中已重命名为socketserver2to3工具在将源转换为 Python 3 时将自动适应导入。

源代码: Lib/SocketServer.py


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

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

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

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

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

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

20.17.1. 服务器创建说明

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

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

请注意,UnixDatagramServer是从UDPServer而不是UnixStreamServer派生的-IP 和 Unix 流服务器之间的唯一区别是地址族,这在两个 Unix 服务器类中都可以重复。

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

混合类首先出现,因为它会覆盖UDPServer中定义的方法。设置各种属性还可以更改基础服务器机制的行为。

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

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

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

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

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

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

20.17.2. 服务器对象

2.6 版的新Function。

2.6 版的新Function。

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

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

20.17.3. 请求处理程序对象

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

20.17.4. Examples

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

使用流的替代请求处理程序类(类似于文件的对象,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)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(data + "\n")

    # Receive data from the server and shut down
    received = sock.recv(1024)
finally:
    sock.close()

print "Sent:     {}".format(data)
print "Received: {}".format(received)

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

Server:

$ python TCPServer.py
127.0.0.1 wrote:
hello world with TCP
127.0.0.1 wrote:
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

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

这是 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(data + "\n", (HOST, PORT))
received = sock.recv(1024)

print "Sent:     {}".format(data)
print "Received: {}".format(received)

该示例的输出应与 TCP 服务器示例完全相同。

20.17.4.3. 异步混合

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

ThreadingMixIn类的示例:

import socket
import threading
import SocketServer

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):

    def handle(self):
        data = self.request.recv(1024)
        cur_thread = threading.current_thread()
        response = "{}: {}".format(cur_thread.name, data)
        self.request.sendall(response)

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

def client(ip, port, message):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((ip, port))
    try:
        sock.sendall(message)
        response = sock.recv(1024)
        print "Received: {}".format(response)
    finally:
        sock.close()

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类的使用方式相同,不同之处在于服务器将为每个请求生成一个新进程。仅在支持fork()的 POSIX 平台上可用。

首页