socketserver --- 用于網(wǎng)絡(luò)服務(wù)器的框架?

源代碼: Lib/socketserver.py


socketserver 模塊簡化了編寫網(wǎng)絡(luò)服務(wù)器的任務(wù)。

該模塊具有四個基礎(chǔ)實體服務(wù)器類:

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

該類使用互聯(lián)網(wǎng) TCP 協(xié)議,它可以提供客戶端與服務(wù)器之間的連續(xù)數(shù)據(jù)流。 如果 bind_and_activate 為真值,該類的構(gòu)造器會自動嘗試發(fā)起調(diào)用 server_bind()server_activate()。 其他形參會被傳遞給 BaseServer 基類。

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)?

該類使用數(shù)據(jù)包,即一系列離散的信息分包,它們可能會無序地到達或在傳輸中丟失。 該類的形參與 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 系統(tǒng)平臺上不可用。 它們的形參與 TCPServer 的相同。

這四個類會 同步地 處理請求;每個請求必須完成才能開始下一個請求。 這就不適用于每個請求要耗費很長時間來完成的情況,或者因為它需要大量的計算,又或者它返回了大量的數(shù)據(jù)而客戶端處理起來很緩慢。 解決方案是創(chuàng)建單獨的進程或線程來處理每個請求;ForkingMixInThreadingMixIn 混合類可以被用于支持異步行為。

創(chuàng)建一個服務(wù)器需要分幾個步驟進行。 首先,你必須通過子類化 BaseRequestHandler 類并重載其 handle() 方法來創(chuàng)建一個請求處理句柄類;這個方法將處理傳入的請求。 其次,你必須實例化某個服務(wù)器類,將服務(wù)器地址和請求處理句柄類傳給它。 建議在 with 語句中使用該服務(wù)器。 然后再調(diào)用服務(wù)器對象的 handle_request()serve_forever() 方法來處理一個或多個請求。 最后,調(diào)用 server_close() 來關(guān)閉套接字(除非你使用了 with 語句)。

當從 ThreadingMixIn 繼承線程連接行為時,你應(yīng)當顯式地聲明你希望在突然關(guān)機時你的線程采取何種行為。 ThreadingMixIn 類定義了一個屬性 daemon_threads,它指明服務(wù)器是否應(yīng)當?shù)却€程終止。 如果你希望線程能自主行動你應(yīng)當顯式地設(shè)置這個旗標;默認值為 False,表示 Python 將不會在 ThreadingMixIn 所創(chuàng)建的所有線程都退出之前退出。

服務(wù)器類具有同樣的外部方法和屬性,無論它們使用哪種網(wǎng)絡(luò)協(xié)議。

服務(wù)器創(chuàng)建的說明?

在繼承圖中有五個類,其中四個代表四種類型的同步服務(wù)器:

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

請注意 UnixDatagramServer 派生自 UDPServer,而不是 UnixStreamServer --- IP 和 Unix 流服務(wù)器的唯一區(qū)別是地址族,它會在兩種 Unix 服務(wù)器類中簡單地重復。

class socketserver.ForkingMixIn?
class socketserver.ThreadingMixIn?

每種服務(wù)器類型的分叉和線程版本都可以使用這些混合類來創(chuàng)建。 例如,ThreadingUDPServer 的創(chuàng)建方式如下:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

混合類先出現(xiàn),因為它重載了 UDPServer 中定義的一個方法。 設(shè)置各種屬性也會改變下層服務(wù)器機制的行為。

ForkingMixIn 和下文提及的分叉類僅在支持 fork() 的 POSIX 系統(tǒng)平臺上可用。

socketserver.ForkingMixIn.server_close() 會等待直到所有子進程完成,除非 socketserver.ForkingMixIn.block_on_close 屬性為假值。

socketserver.ThreadingMixIn.server_close() 會等待直到所有非守護類線程完成,除非 socketserver.ThreadingMixIn.block_on_close 屬性為假值。 請將 ThreadingMixIn.daemon_threads 設(shè)為 True 來使用守護類線程以便不等待線完成。

在 3.7 版更改: socketserver.ForkingMixIn.server_close()socketserver.ThreadingMixIn.server_close() 現(xiàn)在會等待直到所有子進程和非守護類線程完成。 請新增一個 socketserver.ForkingMixIn.block_on_close 類屬性來選擇 3.7 版之前的行為。

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

這些類都是使用混合類來預定義的。

要實現(xiàn)一個服務(wù),你必須從 BaseRequestHandler 派生一個類并重定義其 handle() 方法。 然后你可以通過組合某種服務(wù)器類型與你的請求處理句柄類來運行各種版本的服務(wù)。 請求處理句柄類對于數(shù)據(jù)報和流服務(wù)必須是不相同的。 這可以通過使用處理句柄子類 StreamRequestHandlerDatagramRequestHandler 來隱藏。

當然,你仍然需要動點腦筋! 舉例來說,如果服務(wù)包含可能被不同請求所修改的內(nèi)存狀態(tài)則使用分叉服務(wù)器是沒有意義的,因為在子進程中的修改將永遠不會觸及保存在父進程中的初始狀態(tài)并傳遞到各個子進程。 在這種情況下,你可以使用線程服務(wù)器,但你可能必須使用鎖來保護共享數(shù)據(jù)的一致性。

另一方面,如果你是在編寫一個所有數(shù)據(jù)保存在外部(例如文件系統(tǒng))的 HTTP 服務(wù)器,同步類實際上將在正在處理某個請求的時候“失聰” -- 如果某個客戶端在接收它所請求的所有數(shù)據(jù)時很緩慢這可能會是非常長的時間。 這時線程或分叉服務(wù)器會更為適用。

在某些情況下,合適的做法是同步地處理請求的一部分,但根據(jù)請求數(shù)據(jù)在分叉的子進程中完成處理。 這可以通過使用一個同步服務(wù)器并在請求處理句柄類 handle() 中進行顯式分叉來實現(xiàn)。

還有一種可以在既不支持線程也不支持 fork() 的環(huán)境(或者對于本服務(wù)來說這兩者開銷過大或是不適用)中處理多個同時請求的方式是維護一個顯式的部分完成的請求表并使用 selectors 來決定接下來要處理哪個請求(或者是否要處理一個新傳入的請求)。 這對于流服務(wù)來說特別重要,因為每個客戶端可能會連接很長的時間(如果不能使用線程或子進程)。 請參閱 asyncore 來了解另一種管理方式。

Server 對象?

class socketserver.BaseServer(server_address, RequestHandlerClass)?

這是本模塊中所有 Server 對象的超類。 它定義了下文給出的接口,但沒有實現(xiàn)大部分的方法,它們應(yīng)在子類中實現(xiàn)。 兩個形參存儲在對應(yīng)的 server_addressRequestHandlerClass 屬性中。

fileno()?

返回服務(wù)器正在監(jiān)聽的套接字的以整數(shù)表示的文件描述符。 此函數(shù)最常被傳遞給 selectors,以允許在同一進程中監(jiān)控多個服務(wù)器。

handle_request()?

處理單個請求。 此函數(shù)會依次調(diào)用下列方法: get_request(), verify_request()process_request()。 如果用戶提供的處理句柄類的 handle() 方法引發(fā)了異常,則將調(diào)用服務(wù)器的 handle_error() 方法。 如果在 timeout 秒內(nèi)未接收到請求,將會調(diào)用 handle_timeout() 并將返回 handle_request()。

serve_forever(poll_interval=0.5)?

對請求進行處理直至收到顯式的 shutdown() 請求。 每隔 poll_interval 秒對 shutdown 進行輪詢。 忽略 timeout 屬性。 它還會調(diào)用 service_actions(),這可被子類或混合類用來提供某個給定服務(wù)的專屬操作。 例如,ForkingMixIn 類使用 service_actions() 來清理僵尸子進程。

在 3.3 版更改: service_actions 調(diào)用添加到 serve_forever 方法。

service_actions()?

此方法會在 the serve_forever() 循環(huán)中被調(diào)用。 此方法可被子類或混合類所重載以執(zhí)行某個給定服務(wù)的專屬操作,例如清理操作。

3.3 新版功能.

shutdown()?

通知 serve_forever() 循環(huán)停止并等待它完成。 shutdown() 必須在 serve_forever() 運行于不同線程時被調(diào)用否則它將發(fā)生死鎖。

server_close()?

清理服務(wù)器。 此方法可被重載。

address_family?

服務(wù)器套接字所屬的協(xié)議族。 常見的例子有 socket.AF_INETsocket.AF_UNIX

RequestHandlerClass?

用戶提供的請求處理句柄類;將為每個請求創(chuàng)建該類的實例。

server_address?

服務(wù)器所監(jiān)聽的地址。 地址的格式因具體協(xié)議族而不同;請參閱 socket 模塊的文檔了解詳情。 對于互聯(lián)網(wǎng)協(xié)議,這將是一個元組,其中包含一個表示地址的字符串,和一個表示端口號的整數(shù),例如: ('127.0.0.1', 80)。

socket?

將由服務(wù)器用于監(jiān)聽入站請求的套接字對象。

服務(wù)器類支持下列類變量:

allow_reuse_address?

服務(wù)器是否要允許地址的重用。 默認值為 False,并可在子類中設(shè)置以改變策略。

request_queue_size?

請求隊列的長度。 如果處理單個請求要花費很長的時間,則當服務(wù)器正忙時到達的任何請求都會被加入隊列,最多加入 request_queue_size 個請求。 一旦隊列被加滿,來自客戶端的更多請求將收到 "Connection denied" 錯誤。 默認值為 5,但可在子類中重載。

socket_type?

服務(wù)器使用的套接字類型;常見的有 socket.SOCK_STREAMsocket.SOCK_DGRAM 這兩個值。

timeout?

超時限制,以秒數(shù)表示,或者如果不限制超時則為 None。 如果在超時限制期間沒有收到 handle_request(),則會調(diào)用 handle_timeout() 方法。

有多個服務(wù)器方法可被服務(wù)器基類的子類例如 TCPServer 所重載;這些方法對服務(wù)器對象的外部用戶來說并無用處。

finish_request(request, client_address)?

通過實例化 RequestHandlerClass 并調(diào)用其 handle() 方法來實際處理請求。

get_request()?

必須接受來自套接字的請求,并返回一個 2 元組,其中包含用來與客戶端通信的 new 套接字對象,以及客戶端的地址。

handle_error(request, client_address)?

此函數(shù)會在 RequestHandlerClass 實例的 handle() 方法引發(fā)異常時被調(diào)用。 默認行為是將回溯信息打印到標準錯誤并繼續(xù)處理其他請求。

在 3.6 版更改: 現(xiàn)在只針對派生自 Exception 類的異常調(diào)用此方法。

handle_timeout()?

此函數(shù)會在 timeout 屬性被設(shè)為 None 以外的值并且在超出時限之后仍未收到請求時被調(diào)用。 分叉服務(wù)器的默認行為是收集任何已退出的子進程狀態(tài),而在線程服務(wù)器中此方法則不做任何操作。

process_request(request, client_address)?

調(diào)用 finish_request() 來創(chuàng)建 RequestHandlerClass 的實例。 如果需要,此函數(shù)可創(chuàng)建一個新的進程或線程來處理請求;ForkingMixInThreadingMixIn 類能完成此任務(wù)。

server_activate()?

由服務(wù)器的構(gòu)造器調(diào)用以激活服務(wù)器。 TCP 服務(wù)器的默認行為只是在服務(wù)器的套接字上發(fā)起調(diào)用 listen()。 可以被重載。

server_bind()?

由服務(wù)器的構(gòu)造器調(diào)用以將套接字綁定到所需的地址。 可以被重載。

verify_request(request, client_address)?

必須返回一個布爾值;如果值為 True,請求將被處理。 而如果值為 False,請求將被拒絕。 此函數(shù)可被重載以實現(xiàn)服務(wù)器的訪問控制。 默認實現(xiàn)總是返回 True。

在 3.6 版更改: 添加了對 context manager 協(xié)議的支持。 退出上下文管理器與調(diào)用 server_close() 等效。

請求處理句柄對象?

class socketserver.BaseRequestHandler?

這是所有請求處理句柄對象的超類。 它定義了下文列出的接口。 一個實體請求處理句柄子類必須定義新的 handle() 方法,并可重載任何其他方法。 對于每個請求都會創(chuàng)建一個新的子類的實例。

setup()?

會在 handle() 方法之前被調(diào)用以執(zhí)行任何必要的初始化操作。 默認實現(xiàn)不執(zhí)行任何操作。

handle()?

此函數(shù)必須執(zhí)行為請求提供服務(wù)所需的全部操作。 默認實現(xiàn)不執(zhí)行任何操作。 它有幾個可用的實例屬性;請求為 self.request;客戶端地址為 self.client_address;服務(wù)器實例為 self.server,如果它需要訪問特定服務(wù)器信息的話。

針對數(shù)據(jù)報或流服務(wù)的 self.request 類型是不同的。 對于流服務(wù),self.request 是一個套接字對象;對于數(shù)據(jù)報服務(wù),self.request 是一對字符串與套接字。

finish()?

handle() 方法之后調(diào)用以執(zhí)行任何需要的清理操作。 默認實現(xiàn)不執(zhí)行任何操作。 如果 setup() 引發(fā)了異常,此函數(shù)將不會被調(diào)用。

class socketserver.StreamRequestHandler?
class socketserver.DatagramRequestHandler?

BaseRequestHandler 子類重載了 setup()finish() 方法,并提供了 self.rfileself.wfile 屬性。 self.rfileself.wfile 屬性可以被分別讀取或?qū)懭?,以獲取請求數(shù)據(jù)或?qū)?shù)據(jù)返回給客戶端。

這兩個類的 rfile 屬性都支持 io.BufferedIOBase 可讀接口,并且 DatagramRequestHandler.wfile 還支持 io.BufferedIOBase 可寫接口。

在 3.6 版更改: StreamRequestHandler.wfile 也支持 io.BufferedIOBase 可寫接口。

例子?

socketserver.TCPServer 示例?

以下是服務(wù)端:

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

區(qū)別在于第二個處理句柄的 readline() 調(diào)用將多次調(diào)用 recv() 直至遇到一個換行符,而第一個處理句柄的單次 recv() 調(diào)用只是返回在一次 sendall() 調(diào)用中由客戶端發(fā)送的內(nèi)容。

以下是客戶端:

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

這個示例程序的輸出應(yīng)該是像這樣的:

服務(wù)器:

$ 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 示例?

以下是服務(wù)端:

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

這個示例程序的輸出應(yīng)該是與 TCP 服務(wù)器示例相一致的。

異步混合類?

要構(gòu)建異步處理句柄,請使用 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()

這個示例程序的輸出應(yīng)該是像這樣的:

$ 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 類的使用方式是相同的,區(qū)別在于服務(wù)器將為每個請求產(chǎn)生一個新的進程。 僅在支持 fork() 的 POSIX 系統(tǒng)平臺上可用。