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

源代碼: Lib/socketserver.py


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

該模塊具有四個(gè)基礎(chǔ)實(shí)體服務(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ù)包,即一系列離散的信息分包,它們可能會無序地到達(dá)或在傳輸中丟失。 該類的形參與 TCPServer 的相同。

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

這兩個(gè)更常用的類與 TCP 和 UDP 類相似,但使用 Unix 域套接字;它們在非 Unix 系統(tǒng)平臺上不可用。 它們的形參與 TCPServer 的相同。

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

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

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

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

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

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

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

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

class socketserver.ForkingMixIn?
class socketserver.ThreadingMixIn?

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

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

混合類先出現(xiàn),因?yàn)樗剌d了 UDPServer 中定義的一個(gè)方法。 設(shè)置各種屬性也會改變下層服務(wù)器機(jī)制的行為。

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

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

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

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

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

這些類都是使用混合類來預(yù)定義的。

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

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

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

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

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

Server 對象?

class socketserver.BaseServer(server_address, RequestHandlerClass)?

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

fileno()?

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

handle_request()?

處理單個(gè)請求。 此函數(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)?

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

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

service_actions()?

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

3.3 新版功能.

shutdown()?

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

server_close()?

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

address_family?

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

RequestHandlerClass?

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

server_address?

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

socket?

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

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

allow_reuse_address?

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

request_queue_size?

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

socket_type?

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

timeout?

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

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

finish_request(request, client_address)?

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

get_request()?

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

handle_error(request, client_address)?

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

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

handle_timeout()?

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

process_request(request, client_address)?

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

server_activate()?

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

server_bind()?

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

verify_request(request, client_address)?

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

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

請求處理句柄對象?

class socketserver.BaseRequestHandler?

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

setup()?

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

handle()?

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

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

finish()?

handle() 方法之后調(diào)用以執(zhí)行任何需要的清理操作。 默認(rèn)實(shí)現(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ù)返回給客戶端。

這兩個(gè)類的 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()

一個(gè)使用流(通過提供標(biāo)準(zhǔn)文件接口來簡化通信的文件類對象)的替代請求處理句柄類:

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ū)別在于第二個(gè)處理句柄的 readline() 調(diào)用將多次調(diào)用 recv() 直至遇到一個(gè)換行符,而第一個(gè)處理句柄的單次 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))

這個(gè)示例程序的輸出應(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))

這個(gè)示例程序的輸出應(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()

這個(gè)示例程序的輸出應(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ù)器將為每個(gè)請求產(chǎn)生一個(gè)新的進(jìn)程。 僅在支持 fork() 的 POSIX 系統(tǒng)平臺上可用。