溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

python 之 SocketServer

發(fā)布時間:2020-07-03 04:12:58 來源:網(wǎng)絡(luò) 閱讀:1001 作者:huwenzheng 欄目:建站服務(wù)器

20.17. SocketServer——網(wǎng)絡(luò)框架

注意:SocketServer 在 python 3 中更名為 socketserver。 在將代碼轉(zhuǎn)換為 python 3 的版本時,2to3 工具會自動進(jìn)行導(dǎo)入適配。


源碼:Lib/SocketServer.py


SocketServer 模塊簡化了編寫網(wǎng)絡(luò)服務(wù)器應(yīng)用的步驟。它有四個具體的基礎(chǔ)服務(wù)器類:

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

該類使用 TCP 網(wǎng)絡(luò)協(xié)議,在客戶端和服務(wù)器之間提供持續(xù)的流式數(shù)據(jù)如果 bind_and_activate 為真,則構(gòu)造方法會自動調(diào)用 server_bind() 和 server_activate() 方法。其余參數(shù)傳給基類 BaseServer。

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

該類使用數(shù)據(jù)報文,而且報文包是離散的;在數(shù)據(jù)傳輸過程中,包到達(dá)的次序可能會錯亂,或者出現(xiàn)丟失的情況。類參數(shù)和 TCPServer 一樣。

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

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


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


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


若想繼承基類 ThreadingMixIn 完成多線程連接行為,你需要顯式地聲明線程在遇到突然關(guān)閉時如何處理。ThreadingMixIn 類有一個名為 daemon_threads 的屬性,表示線程如果終止,服務(wù)器是否應(yīng)該等待。如果你希望線程自動處理,則必須顯式設(shè)置該標(biāo)志;默認(rèn)情況下,其值為 False,表示只有當(dāng)所有 ThreadingMixIn 創(chuàng)建的線程退出,Python 才會退出。


無論采用何種類型的網(wǎng)絡(luò)協(xié)議,服務(wù)器類都有相同的外部方法和屬性。

20.17.1. 服務(wù)器創(chuàng)建要點

繼承的關(guān)系表格中有 5 個類,其中四種類型代表四個同步方式的服務(wù)器類。

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

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

class SocketServer.ForkingMixIn
class SocketServer.ThreadingMixIn

每種類型的服務(wù)器都可以使用 mix-in 類創(chuàng)建多進(jìn)程或者多線程。例如下面的例子,創(chuàng)建一個 ThreadingUDPServer 類:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

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


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

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

這四個類是預(yù)定義的 mix-in 類。


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


當(dāng)然,你也可以自主發(fā)揮。比如,如果服務(wù)在內(nèi)存中的狀態(tài)可以被不同的請求修改,而子進(jìn)程的修改如果根本到不了父進(jìn)程的初始狀態(tài)并傳給子進(jìn)程,那么創(chuàng)建一個服務(wù)器的子進(jìn)程毫無意義。在這種情況下,你可以創(chuàng)建一個服務(wù)器的子線程,但為保證共享數(shù)據(jù)的完整性,可能需要加鎖。


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


某些情況下,基于請求數(shù)據(jù),將請求的一部分內(nèi)容作同步處理,而將剩余部分放在子進(jìn)程中處理是可以的。可以創(chuàng)建一個同步服務(wù)器,而在 handle() 方法中 fork 一個子進(jìn)程完成上述動作。


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

20.17.2. 服務(wù)器對象

class SocketServer.BaseServer(server_address, RequestHandlerClass)

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

fileno()

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

handle_request()

處理單一請求。這個函數(shù)按順序調(diào)用以下方法:get_request(), verify_request() 和 process_request()。如果用戶提供的請求解析類 handle() 方法拋出了異常,則服務(wù)器會調(diào)用 handle_error() 方法。如果在 timeout 秒內(nèi)沒有收到請求,則會調(diào)用 handle_timeout() 方法,同時 handle_request() 返回。

serve_forever(poll_interval=0.5

解析請求,直至接收到一個顯示的 shutdown() 請求為止。每隔 poll_interval 秒會檢查一下是否收到 shutdown 請求。忽略 timeout 屬性。如果你需要進(jìn)行周期性的任務(wù),請在另一個線程里執(zhí)行。

shutdown()

告訴 serve_forever() 停止循環(huán),并等待 serve_forever() 停止之后才返回。

server_close()

清理服務(wù)器??梢员蛔宇惛膶?。

address_family

服務(wù)器的 socket 所屬的協(xié)議族。常用值為 socket.AF_INET 和 socket.AF_UNIX

RequestHandlerClass

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

server_address

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

socket

服務(wù)器監(jiān)聽請求時使用的 socket 對象


服務(wù)器類支持以下類屬性:

allow_resue_address

無論服務(wù)器是否允許地址重用,該值默認(rèn)為 False,可以在子類中修改。

request_queue_size

請求隊列的大小。如果處理單一請求時間很長,則當(dāng)服務(wù)器忙的時候,任何到達(dá)的請求會進(jìn)入到一個隊列中,直到個數(shù)達(dá)到 request_queue_size。一旦隊列滿了,那么后來的請求會收到 "Connection denied" 的錯誤回饋。默認(rèn)值通常是 5,但是可以在子類進(jìn)行修改。

socket_type

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

timeout

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


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

finish_request()

創(chuàng)建 RequestHandlerClass 對象并真正處理請求,調(diào)用解析類 handle() 的方法。

get_request()

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

handle_error(request, client_address)

如果 RequestHandlerClass 對象的 handle() 函數(shù)拋出了異常,則會調(diào)用 handle_error()。handle_error 默認(rèn)行為是將異常調(diào)用棧打印到標(biāo)準(zhǔn)輸出終端上,并繼續(xù)解析下一個請求。

handle_timeout()

當(dāng) timeout 屬性不是 None,而是一個有效值,而經(jīng)過 timeout 時間之后還是沒有收到請求時,會調(diào)用這個函數(shù)。handle_timeout 的默認(rèn)行為是 fork 一個服務(wù)器的子進(jìn)程用于收集任何退出的子進(jìn)程狀態(tài),而在服務(wù)器的多個線程中,該方法什么也不做。

process_request(request, client_addresss)

調(diào)用 finish_request() 創(chuàng)建一個 RequestHandlerClass 對象。如果需要,這個函數(shù)會創(chuàng)建一個新的進(jìn)程或者線程用于解析請求;ForkingMixIn 和 ThreadingMixIn 類會完成這個動作。

server_activate()

由類的構(gòu)造方法調(diào)用,用于激活服務(wù)器。TCP 服務(wù)器里的 server_activate() 默認(rèn)只會調(diào)用 listen() 方法,用于監(jiān)聽服務(wù)器的 socket 對象。該方法可以被子類改寫。

server_bind()

由類的構(gòu)造方法調(diào)用,用于將 socket 對象綁定到指定地址上。該方法可以被子類改寫。

verify_request(request, client_address)

必須返回一個布爾值;如果值為 True,則處理請求,否則拒絕處理。verify_request 可以被子類改寫,憑借該方法,服務(wù)器可以實現(xiàn)訪問控制。該方法默認(rèn)返回 True。

20.17.3.請求解析類對象

class SocketServer.BaseRequestHandler

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

setup()

在 handle() 函數(shù)之前調(diào)用,用于初始化行為。默認(rèn)什么也不做

handle()

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


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

finish()

在 handle() 方法調(diào)用之后執(zhí)行所有清理操作。默認(rèn)什么也不做。如果 setup() 拋出異常,那么這個函數(shù)不會被執(zhí)行。


class SocketServer.StreamRequestHandler

class SocketServer.DatagramRequestHandler


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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI