Python網絡編程(3)——SocketServer模塊與簡單並發服務器


 

主要類型

  該模塊有四個比較主要的類,其中常用的是 TCPServer 和 UDPServer。

  1. TCPServer

  2. UDPServer

  3. UnixStreamServer,類似於TCPServer提供面向數據流的套接字連接,但是旨在UNIX平台上可用;

  4. UnixDatagramServer,類似於UDPServer提供面向數據報的套接字連接,但是旨在UNIX平台上可用;

  這四個類型同步地處理請求,也就是說一個請求沒有完成之前是不會處理下一個請求的,這種模式當然不適合生產環境,一個客戶端連接就可能拖延所有的執行。所以這個模塊還提供了兩種支持異步處理的類: 

  5. ForkingMixIn,為每一個客戶端請求派生一個新的進程去專門處理;

  6. ThreadingMixIn,為每一個客戶端請求派生一個新的線程去專門處理;

  繼承自這兩個類型的服務端在處理新的客戶端連接時不會阻塞,而是創建新的進/線程專門處理客戶端的請求。

編程框架 

  首先從高層面介紹一下使用SocketServer模塊開發多進程/線程 異步服務器的流程:

  1. 根據需要選擇一個合適的服務類型,如,面向TCP連接多進程服務器:  ForkingTCPServer 

  2. 創建一個請求處理器(request handler)類型,這個類型的 handle()(類似於回調函數)方法中定義如何處理到達的客戶端連接。

  3. 實例化服務器,傳入服務器綁定的地址和第2步定義的請求處理器類

  4. 調用服務器實例的 handle_request() 或 serve_forever() 方法,一次或多次處理客戶請求。

 

具體流程

1. 選擇合適的服務器類型

  上文介紹了SocketServer模塊提供的幾種主要的基本服務類型和兩種MixIn類型,就是構造適合需求的多線程/進程服務器的原料。是否使用、如何使用這兩個MixIn類型是由程序員決定的,利用的就是Python的多重繼承機制,將MixIn類型作為代碼庫(而不是初始化實例的工具),為實例提供新的方法。

  SocketServer模塊提供的主要服務類型和兩種MixIn類型,可以有以下的組合:

 

TCPServer

UDPServer
ForkingMixIn ForkingTCPServer

ForkingUDPServer

ThreadingMixIn ThreadingTCPServer

ThreadingUDPServer

 

  只要根據需要選擇特定類型的server類型即可(例如 ThreadingTCPServer,面向TCP連接的多線程服務器),即便自己定義多進程/線程的server類型,也不過多重繼承對應的連接server類和MinIn而已。比如,面向TCP連接線程式異步服務器類型,實際上就是:

class ForkingTCPServer(FrokingMixIn, TCPServer):
pass

  Python 的多重繼承機制保證了這里只要繼承了必要的父類就可以完成目標類型的定義,不需要添加額外的內容。

 

2. 定義請求處理器

  SocketServer模塊提供了 BaseRequestHandler 類型用於定制Handler類型,自定義的Handler類型只要繼承自 BaseRequestHandler 並覆寫它的 handle() 方法即可。handle() 方法定義如何處理客戶端的請求,服務器只是封裝了socket對象的眾多操作流程以及進程、線程等的管理,然后對於每一個客戶端請求調用handle() 方法。handle() 方法就是server為client創建新的線程后調用的回調函數。

 

  BaseRequestHandler 實例的一些屬性非常有用,可以用來獲得一些和連接相關的信息,包括客戶端套接字的地址、服務端當前連接的socket對象等:

(1)獲取client端socket對象的地址

h.client_address

  h.client_address 是 client 的地址,IPv4地址族中就是 (host, port) 二元組。該屬性由基類在連接建立時設置。

 

(2)獲取創建自己的 server 對象

h.server

  該屬性保存創建這個 BaseRequestHandler 實例的 server 對象。

 

(3)從 BaseRequestHandler 實例獲取連接套接字

h.request
  • 對 TCP server,h.request 屬性是連接到 client 的連接套接字對象;
  • 對 UDP server,h.request 屬性是一個二元組(data, sock),data 是 client 端發送的數據(最大8192字節),sock是server端套接字。

  使用這個屬性可以獲取在這個進/線程中與client套接字建立連接的連接套接字,從而可以使用這個套接字與client端通信。

   StreamRequestHandler 和 DatagramRequestHandler 則屏蔽了 self.request 對TCP和UDP連接的區別,二者都重定義了 setup() 和 finish() 方法,提供統一的 self.rfile 和 self.wfile 屬性以便從客戶端讀取數據或向客戶端發送數據。

 

  BaseRequestHandler 實例 h 提供如下的接口,他們都可以根據需要重寫:

(1)初始化

BaseRequestHandler.setup()

  在 handle() 方法之前調用 setup() ,完成一些初始化的工作,默認什么也不做。

  

(2)回調函數 handle()

BaseRequestHandler.handle()

  handle() 完成所有的對於每個請求的處理工作,也就是實現服務端的業務邏輯,默認情況下什么也不做。要與 client 端通信,最終還是要通過建立連接的套接字對象,這里可以使用 h.request 屬性獲取連接套接字,對於TCP服務器和UDP服務器的區別,參考 h.request 屬性。

  

(3)終止化

BaseRequestHandler.finish()

  作用:在handle() 方法之后調用,完成一些清理的工作,默認的情形下什么也不做。如果setup()方法拋出異常,那么該方法不會被調用。

 

 3. 實例化服務器

  實例化服務器時傳入服務器需要綁定的地址是必要的,另一方面還應該傳入自定義的Handler類型,服務器實例將對每一個客戶端連接調用它的 handle() 方法。

  例如:

server = ForkingTCPServer((host, port), MyRequestHandler)

 

4. 調用服務器實例的處理方法

  服務器實例的 handle_request() 方法與 serve_forever() 方法分別用於單次處理或一直處理請求,可以直接在腳本中調用這些方法,也可以在新的進程或者線程中調用這些方法,啟動服務器:

import threading

...

server = ForkingTCPServer((host, port), MyRequestHandler)
server_thread = threading.Thread(target = server.serve_forever)
server_thread.start()

   則在一個新的線程中創建一個 多進程的TCP服務器,每當一個新的連接到來,他都會創建一個新的進程去服務client端的請求。

 

實例:

  該例子使用 SocketServer 模塊實現一個簡單的多線程 TCP 服務器

import SocketServer
class EchoHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        print("Connected from: ", self.client_address)
        while True:
            recvData = self.request.recv(1024)
            if not recvData:
                break
            self.request.sendall(recvData)
        self.request.close()
        print("Disconnected from: ", self.client_address)

srv = SocketServer.ThreadingTCPServer(("", 4424), EchoHandler)
srv.serve_forever()

   該例在當前線程中創建一個多線程TCP服務器,其功能是將客戶端發送的數據回顯給客戶。

   可見,在handle()中定義服務器的業務邏輯,任何需要對連接socket對象的操作,都可以通過 self.request 屬性操作,在TCP連接中,這個屬性就是server端的socket對象。在定義完Handler類型后,將其作為參數傳給選擇的server類型即可。

  該多線程TCP server 的運行示例:

('Connected from: ', ('127.0.0.1', 63235))
('Connected from: ', ('127.0.0.1', 63236))
('Disconnected from: ', ('127.0.0.1', 63235))
('Disconnected from: ', ('127.0.0.1', 63236))

  這里發起兩個客戶端同時請求服務端,發現服務端確實具有了基本的並發能力。


 

補充:

  SocketServer 中的 TCPServer、UDPServer 提供的可供使用的屬性、方法(實際上都來自於其父類 SocketServer.BaseServer ):

屬性:

 BaseServer.address_family 

  內容:服務器套接字對象的地址族,如 socket.AF_INET 、 socket.AF_UNIX 等。

 

 BaseServer.RequestHandlerClass 

  內容:用戶自定義,傳給服務器構造函數Handler類型,服務器會為每一個請求創建一個該類型的實例。

 

 BaseServer.server_address 

  內容:服務器監聽的地址,具體的形式依賴於地址族,如AF_INET形式的 ('127.0.0.1', 80) 等。

 

 BaseServer.socket 

  內容:服務器監聽的套接字對象

 

 BaseServer.allow_reuse_address 

  內容:是否允許地址重用,默認為False,子類可以更改。

 

 BaseServer.request_queue_size 

  內容:請求隊列的長度,一旦等待服務的請求數達到這個限制,后續到來的請求收到“Connection denied”錯誤,通常該值默認為5,子類可以覆寫。

  

 BaseServer.socket_type 

  內容:服務器所用套接字的類型,如 socket.SOCK_STREAM 和 socket.SOCK_DGRAM 。

 

 BaseServer.timeout 

  內容:服務器的超時限制,如果是None,那么沒有設置超時;如果在指定的時限內 handle_request() 方法沒有獲得請求,那么將會調用 handle_timeout() 方法。

 

 BaseServer.fileno() 

  內容:返回服務器所用套接字對象的fd,一種典型的用法是傳給 select.select() 方法便於在一個進程中監控多個服務器。

 

方法:

 BaseServer.server_bind()

  作用:由實例的構造函數調用,將套接字綁定到目標地址,可以覆寫。

 

 BaseServer.server_activate() 

  作用:由實例的構造函數調用,監聽服務器套接字,可以覆寫。

 

 BaseServer.handle_request() 

  作用:處理單個請求,依次調用 get_request() 、 verify_request() 和 process_request() 方法,如果用戶自定義的handle()方法拋出異常,則調用 handle_error() 方法,如果超過 self.timeout 秒沒有獲得請求,調用 handle_timeout() 方法,然后 handle_request() 方法返回。

 

   BaseServer.get_request()

    作用:服務器對象的使用者不一定需要直接調用該方法,從套接字接受一個請求。返回一個二元組,首元是新的已經連接的套接字對象,次元是客戶端的地址。

 

   BaseServer.verify_request(request, client_address) 

    作用:服務器對象的使用者不一定需要直接調用該方法。返回一個布爾值,為True時處理請求,為False時拒絕請求,可以通過覆寫該方法為服務器實現訪問控制。默認的實現總是返回True。

 

   BaseServer.process_request(request, client_address) 

    作用:該方法體現MixIn的作用,此處可能會創建線程或進程來處理用戶的請求,最終都是該方法通過調用 finish_request() 來為每個請求實例化一個用戶自定義的Handler實例,然后投到新建的線程或進程中,處理用戶的請求。

 

     BaseServer.finish_request() 

      作用:為每個請求實例化一個用戶自定義的Handler實例,並調用其 handle() 方法。

 

   BaseServer.handle_error(request, client_address) 

    作用:當用戶自定義的Handler實例的 handle() 方法拋出異常時,調用該方法。默認的工作是將traceback打印到標准輸出,然后繼續處理請求。

 

   BaseServer.handle_timeout() 

     作用:當self.timeout屬性規定的超時上限達到時(不是None)還沒有等到請求。多進程服務器的默認行為是收集所有已經退出的子進程的狀態,多線程服務器默認什么也不做。

 

 BaseServer.serve_forever(poll_interval=0.5) 

  作用:一直處理請求,直到顯式調用 shutdown() 函數,每隔 poll_interval (默認0.5)秒輪詢一遍shutdown,該函數無視 self.timeout 。

 

 BaseServer.shutdown() 

  作用:停止 serve_forever() 循環直到其停止。

 


Contact_me: darren_wang_a^t_outlook.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM