python 之 SocketServer

20.17. SocketServer——網絡框架

注意:SocketServer 在 python 3 中更名爲 socketserver。 在將代碼轉換爲 python 3 的版本時,2to3 工具會自動進行導入適配。


源碼:Lib/SocketServer.py


SocketServer 模塊簡化了編寫網絡服務器應用的步驟。它有四個具體的基礎服務器類:

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

該類使用 TCP 網絡協議,在客戶端和服務器之間提供持續的流式數據如果 bind_and_activate 爲真,則構造方法會自動調用 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)

這兩個類使用得不如前兩個類頻繁,其用法類似,只是採用的是 Unix 域下的 sockets;它們不能在非 Unix 平臺上使用。類參數和 TCPServer 一樣。


以上四個類均採用同步方式處理網絡請求,即在處理下一個請求之前必須先處理完本次請求。如果請求耗時很長,比如計算量大,或數據太多引起客戶端處理慢等等,(同步方式)就不太合適了。解決的辦法是創建獨立的進程或者線程用來應對每個請求;mix-in 類 ForkingMixIn 和 ThreadIingMixin 可以支持這種異步行爲。


創建一個服務器需要經歷幾個步驟。首先,你要寫一個請求解析類,繼承自基類 BaseRequestHandler,同時改寫基類的 handle() 方法;這個方法會處理進入服務器的每一個請求。第二步,你必須創建一個服務器類的對象,將服務器地址和請求解析類傳遞給該對象。之後調用服務器對象的 handle_request() 或者 server_forever() 方法處理一個或者多個請求。最後,調用 server_close() 方法關閉 socket。


若想繼承基類 ThreadingMixIn 完成多線程連接行爲,你需要顯式地聲明線程在遇到突然關閉時如何處理。ThreadingMixIn 類有一個名爲 daemon_threads 的屬性,表示線程如果終止,服務器是否應該等待。如果你希望線程自動處理,則必須顯式設置該標誌;默認情況下,其值爲 False,表示只有當所有 ThreadingMixIn 創建的線程退出,Python 纔會退出。


無論採用何種類型的網絡協議,服務器類都有相同的外部方法和屬性。

20.17.1. 服務器創建要點

繼承的關係表格中有 5 個類,其中四種類型代表四個同步方式的服務器類。

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

注意 UnixDatagramServer 繼承自 UDPServer,而不是 UnixStreamServer——IP 和 Unix 流式服務器之間的唯一區別是地址族,兩個 Unix 服務器使用相同的地址族。

class SocketServer.ForkingMixIn
class SocketServer.ThreadingMixIn

每種類型的服務器都可以使用 mix-in 類創建多進程或者多線程。例如下面的例子,創建一個 ThreadingUDPServer 類:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

注意,mix-in 類在前面,因爲它會重寫 UDPServer 中的一個方法。設置不同屬性也可以修改底層服務器的行爲。


以下 ForkingMixIn 和 Forking 類只能在 POSIX 平臺上使用,支持 fork()。

class SocketServer.ForkingTCPServer
class SocketServer.ForkingUDPServer
class SocketServer.ThreadingTCPServer
class SocketServer.ThreadingUDPServer

這四個類是預定義的 mix-in 類。


爲給客戶端提供服務,你必須從 BaseRequestHandler 類中派生子類,並重寫 handle() 方法。之後就可以通過合併某種類型的服務器與 BaseRequestHandler 子類來提供該類型的服務。報文服務和流式服務的請求解析類肯定是不同的。你可以繼承 StreamRequestHandler 或者 DatagramRequestHandler 類來隱藏這種差異。


當然,你也可以自主發揮。比如,如果服務在內存中的狀態可以被不同的請求修改,而子進程的修改如果根本到不了父進程的初始狀態並傳給子進程,那麼創建一個服務器的子進程毫無意義。在這種情況下,你可以創建一個服務器的子線程,但爲保證共享數據的完整性,可能需要加鎖。


另一個方面,如果你創建的一個 HTTP 服務器,其所有數據都存在外部媒介(例如文件系統)上,那麼同步服務器在解析一個因客戶端接收數據慢導致耗時很長的請求時,就基本上只能“耗”在該服務上,兩耳不聞窗外事了。這個時候,創建一個子線程或者子進程就很合適。


某些情況下,基於請求數據,將請求的一部分內容作同步處理,而將剩餘部分放在子進程中處理是可以的。可以創建一個同步服務器,而在 handle() 方法中 fork 一個子進程完成上述動作。


在一個既不支持多線程也不支持 fork 的環境下(或者在特定環境下,它們的代價都太大或者不合適),解析多個同時接收的請求的另一種辦法是維持一張顯式的表,表中記錄部分完成的請求,通過 select() 方法來決定哪一個是即將工作的請求(或者需要解析新請求)。這對於每個客戶端可能會長時間連接的流式服務尤其重要(前提是多線程或者多進程不能使用)。可以去看看 asyncore 模塊,裏面提到另一種方法來處理這種情況。

20.17.2. 服務器對象

class SocketServer.BaseServer(server_address, RequestHandlerClass)

BaseServer 是模塊裏所有服務器對象的父類。它定義了以下接口,但是其中的絕大部分的方法都不需要實現,因爲它們在子類裏都實現過了。需要兩個參數,分別存在 server_address 和 RequestHandlerClass 屬性裏。

fileno()

獲取服務器正在監聽的 socket 文件描述符。該方法最常用的做法是將返回值傳給 select.select() 方法,允許在同一個進程中監聽多個服務器。

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 秒會檢查一下是否收到 shutdown 請求。忽略 timeout 屬性。如果你需要進行週期性的任務,請在另一個線程裏執行。

shutdown()

告訴 serve_forever() 停止循環,並等待 serve_forever() 停止之後才返回。

server_close()

清理服務器。可以被子類改寫。

address_family

服務器的 socket 所屬的協議族。常用值爲 socket.AF_INET 和 socket.AF_UNIX

RequestHandlerClass

用戶提供的請求解析類;每個請求都會創建一個解析類對象。

server_address

服務器正在監聽的地址。地址值的格式取決於協議族;參考 socket 模塊的文檔以獲取更多詳細信息。因特網協議中,服務器地址是一個二元組,它包含指定的地址字符串和一個整型端口號:例如 ( '127.0.0.1',80)

socket

服務器監聽請求時使用的 socket 對象


服務器類支持以下類屬性:

allow_resue_address

無論服務器是否允許地址重用,該值默認爲 False,可以在子類中修改。

request_queue_size

請求隊列的大小。如果處理單一請求時間很長,則當服務器忙的時候,任何到達的請求會進入到一個隊列中,直到個數達到 request_queue_size。一旦隊列滿了,那麼後來的請求會收到 "Connection denied" 的錯誤回饋。默認值通常是 5,但是可以在子類進行修改。

socket_type

服務器使用的 socket 類型;通常取 socket.SOCK_STREAM 和 socket.SOCK_DGRAM

timeout

超時時長,單位是秒,如果不需要超時設置,則爲 None。如果 handle_request() 在超時時段內沒有收到請求,則會調用 handle_timeout() 方法。


有幾個服務器方法可以在子類中(如 TCPServer) 中改寫;這些方法對於服務器對象的使用者而言是沒有什麼用處的。

finish_request()

創建 RequestHandlerClass 對象並真正處理請求,調用解析類 handle() 的方法。

get_request()

必須接受一個 socket 請求,返回一個二元組,包括新的 socket 對象用於和客戶端通信以及客戶端地址

handle_error(request, client_address)

如果 RequestHandlerClass 對象的 handle() 函數拋出了異常,則會調用 handle_error()。handle_error 默認行爲是將異常調用棧打印到標準輸出終端上,並繼續解析下一個請求。

handle_timeout()

當 timeout 屬性不是 None,而是一個有效值,而經過 timeout 時間之後還是沒有收到請求時,會調用這個函數。handle_timeout 的默認行爲是 fork 一個服務器的子進程用於收集任何退出的子進程狀態,而在服務器的多個線程中,該方法什麼也不做。

process_request(request, client_addresss)

調用 finish_request() 創建一個 RequestHandlerClass 對象。如果需要,這個函數會創建一個新的進程或者線程用於解析請求;ForkingMixIn 和 ThreadingMixIn 類會完成這個動作。

server_activate()

由類的構造方法調用,用於激活服務器。TCP 服務器裏的 server_activate() 默認只會調用 listen() 方法,用於監聽服務器的 socket 對象。該方法可以被子類改寫。

server_bind()

由類的構造方法調用,用於將 socket 對象綁定到指定地址上。該方法可以被子類改寫。

verify_request(request, client_address)

必須返回一個布爾值;如果值爲 True,則處理請求,否則拒絕處理。verify_request 可以被子類改寫,憑藉該方法,服務器可以實現訪問控制。該方法默認返回 True。

20.17.3.請求解析類對象

class SocketServer.BaseRequestHandler

它是所有請求解析類對象的父類。有以下接口。請求解析類的子類必須實現 handle() 方法,可以改寫其他方法。每個請求都會創建一個子類對象。

setup()

在 handle() 函數之前調用,用於初始化行爲。默認什麼也不做

handle()

這個函數必須完成所有期望的工作,爲請求提供服務。默認什麼也不做。handle() 可以使用幾個實例屬性;請求實例 self.request;客戶端地址 self.client_address;服務器實例 self.server,以便訪問服務器的信息。


報文服務或者流式服務的 self.request 類型是不一樣的。對於流式服務,self.request 是一個 socket 對象;而報文服務,self.request 是一對字符串加 socket。

finish()

在 handle() 方法調用之後執行所有清理操作。默認什麼也不做。如果 setup() 拋出異常,那麼這個函數不會被執行。


class SocketServer.StreamRequestHandler

class SocketServer.DatagramRequestHandler


這些 BaseRequestHandler 的子類改寫 setup() 和 finish() 方法,同時提供了 self.rfile 和 self.wfile 屬性。self.rfile 是可讀的,用於獲取請求數據;self.wfile 屬性是可寫的,用於提供數據給客戶端。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章