socketserver 源碼閱讀筆記

socketserver 是 python 標(biāo)準(zhǔn)庫中的模塊鸟召,作為一個網(wǎng)絡(luò)服務(wù)器框架(A framework for network servers)拾酝,其作用是簡化編寫網(wǎng)絡(luò)服務(wù)器流程,并且擔(dān)當(dāng)標(biāo)準(zhǔn)庫中其它模塊跟第三方模塊的底層服務(wù)器基礎(chǔ)犬缨。

socketserver 是 python3 中的新名字扮匠,在 python2 中名為 SocketServer吠架,本次閱讀的是 0.4 版本。

該框架主要是使用 ServerClass 跟 RequestHandlerClass 兩大類精居。

  • ServerClass 處理服務(wù)端與客戶端的通訊
  • RequestHandlerClass 處理數(shù)據(jù)的解析诲侮,接收和發(fā)送;主要的業(yè)務(wù)邏輯

ServerClass

  • BaseServer 抽象基類
  • TCPServer 處理流式套接字
  • UnixStreamServer 處理本地處理流式套接字箱蟆,只適用UNIX平臺
  • UDPServer 處理數(shù)據(jù)報套接字
  • UnixDatagramServer 處理本地處理數(shù)據(jù)報套接字沟绪,只適用UNIX平臺

RequestHandlerClass

  • BaseRequestHandler 處理基類
  • StreamRequestHandler 處理流式套接字
  • DatagramRequestHandler 處理數(shù)據(jù)報套接字

通過對通訊和數(shù)據(jù)處理的解耦,可以實現(xiàn)不同組合的服務(wù)器用于處理不同的套接字空猜,但這樣的服務(wù)器绽慈,僅僅是同步處理請求,如果要實現(xiàn)異步處理請求辈毯,還需使用 MixINClass

  • ForkingMixIn 利用多進(jìn)程實現(xiàn)異步坝疼,接收一個請求后,fork 一個進(jìn)程進(jìn)行響應(yīng)處理
  • ThreadingMixIn 利用多線程實現(xiàn)異常谆沃,接收一個請求后钝凶,使用一個線程進(jìn)行響應(yīng)處理

關(guān)于 MixIN,Mixin 掃盲班

Server 類的繼承關(guān)系

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

UDPServer 繼承 TCPServer唁影,TCPServer 繼承 BaseServer耕陷,UnixStreamServer 繼承自 TCPServer,UnixDatagramServer 繼承自 UDPServer据沈。

if hasattr(socket, 'AF_UNIX'):

    class UnixStreamServer(TCPServer):
        address_family = socket.AF_UNIX

    class UnixDatagramServer(UDPServer):
        address_family = socket.AF_UNIX

從源碼看哟沫,UnixStreamServer 跟 TCPServer,UnixDatagramServer 跟 UDPServer锌介,僅僅是改變了 address_family嗜诀。

關(guān)于 socket.AF_UNIX猾警,主要是用于同一臺機器上的進(jìn)程間通信。
AF_INET域與AF_UNIX域socket通信原理對比

if hasattr(os, "fork"):
    class ForkingUDPServer(ForkingMixIn, UDPServer): pass
    class ForkingTCPServer(ForkingMixIn, TCPServer): pass

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

而多進(jìn)程服務(wù)器跟多線程服務(wù)器隆敢,也僅僅是把 TCPServer发皿、UDPServer 跟 ForkingMixIn、ThreadingMixIn 組合在一起而已拂蝎。

class BaseRequestHandler:
    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass

RequestHandler 類的繼承關(guān)系

          +--------------------+        +----------------------+
          | BaseRequestHandler |------->| StreamRequestHandler |
          +--------------------+        +----------------------+
                    |
                    v
        +------------------------+       
        | DatagramRequestHandler |
        +------------------------+ 

StreamRequestHandler 跟 DatagramRequestHandler 是繼承 BaseRequestHandler雳窟,它們都重寫了 setup 跟 finish,本質(zhì)上是用緩存對套接字提供服務(wù)匣屡。

調(diào)用流程

示例 來看調(diào)用流程

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    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

    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        server.serve_forever()

這里定義了一個繼承自 BaseRequestHandler 的 MyTCPHandler封救,重寫了 handle 方法,然后主機捣作、端口一同傳入 TCPServer誉结。

class BaseServer:
    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

class TCPServer(BaseServer):
    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise

1.TCPServer 實例化,調(diào)用了自身 server_bind() 方法券躁,綁定了傳入的 server_address 參數(shù)惩坑,而 server_activate() 方法是重寫自 BaseServer類,主要是請求隊列大小的設(shè)置也拜。

2.啟動服務(wù) 以舒,調(diào)用 BaseServer 類的 serve_forever() 方法,用于不斷監(jiān)聽請求慢哈,當(dāng)有請求時調(diào)用 _handle_request_noblock() 方法完成請求處理蔓钟,然后調(diào)用 service_actions() 處理后的操作;這個方法在 ForkingMixIn 類會被重寫卵贱,用于解決完成請求處理的進(jìn)程滥沫。

    def _handle_request_noblock(self):
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)

_handle_request_noblock() 方法處理請求時,會調(diào)用下列方法

  • get_request() 方法在 TCPServer 類中定義键俱,調(diào)用socket.accept() 方法兰绣,返回 request 參數(shù)和 client_address 參數(shù)
  • verify_request(request, client_address) 方法在 BaseServer 類中定義,可以在子類中重寫做一些驗證處理
  • process_request(request, client_address) 方法請求處理函數(shù)编振,用于調(diào)用 finish_request() 方法缀辩,在 ForkingMixIn 類和 ThreadingMixIn 類中會被重寫,用進(jìn)程或線程發(fā)起處理
  • finish_request(request, client_address) 方法具體的請求處理過程踪央,這里是自定義 MyTCPHandler 類實例化臀玄,該類繼承了 BaseRequestHandler 類,遵傳它的定義杯瞻,當(dāng)自己被實例化時镐牺,會調(diào)用 handle() 方法
  • shutdown_request(request) 方法在 TCPServer 類中定義,會先 request.shutdown(socket.SHUT_WR) 魁莉,出錯再調(diào)用 close_request(request) 方法做 request.close(),而在 UDPServer 類中,該函數(shù)會重寫 pass

3.關(guān)閉服務(wù)旗唁,要結(jié)束服務(wù)畦浓,可以手動調(diào)用 BaseServer 類中的 shutdown() 方法,示例說直接用 Ctrl-C

    def shutdown(self):
        self.__shutdown_request = True
        self.__is_shut_down.wait()

主要是 __shutdown_request 標(biāo)識更換检疫,并阻塞等待 serve_forever() 線程返回

這便是示例中 TCPServer 的生命周期讶请。

些許問題

request.shutdowm(socket.SHUT_WR)request.close() 區(qū)別

1.調(diào)用shutdown會馬上關(guān)閉指定鏈接, 而close會等到描述符的引用計數(shù)器為0時才會開始關(guān)閉鏈接
2.close會同時關(guān)閉兩個鏈接, 而shutdown值關(guān)閉指定鏈接
3.close后文件描述符不再可用(引用基數(shù)為0,釋放資源), shutdown后文件描述符是可用的.

具體

poll屎媳、epoll 選擇

if hasattr(selectors, 'PollSelector'):
    _ServerSelector = selectors.PollSelector
else:
    _ServerSelector = selectors.SelectSelector

##  Lib/selectors.py

# Choose the best implementation, roughly:
#    epoll|kqueue|devpoll > poll > select.
# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
if 'KqueueSelector' in globals():
    DefaultSelector = KqueueSelector
elif 'EpollSelector' in globals():
    DefaultSelector = EpollSelector
elif 'DevpollSelector' in globals():
    DefaultSelector = DevpollSelector
elif 'PollSelector' in globals():
    DefaultSelector = PollSelector
else:
    DefaultSelector = SelectSelector

socketserver 模塊中手動選擇了 poll 庫夺溢,而不是使用模塊提供的 DefaultSelector 自適應(yīng)選擇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烛谊,一起剝皮案震驚了整個濱河市风响,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丹禀,老刑警劉巖状勤,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異双泪,居然都是意外死亡持搜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門焙矛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來葫盼,“玉大人,你說我怎么就攤上這事村斟〖舴担” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵邓梅,是天一觀的道長脱盲。 經(jīng)常有香客問我,道長日缨,這世上最難降的妖魔是什么钱反? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮匣距,結(jié)果婚禮上面哥,老公的妹妹穿的比我還像新娘。我一直安慰自己毅待,他們只是感情好尚卫,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著尸红,像睡著了一般吱涉。 火紅的嫁衣襯著肌膚如雪刹泄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天怎爵,我揣著相機與錄音特石,去河邊找鬼。 笑死鳖链,一個胖子當(dāng)著我的面吹牛姆蘸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芙委,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼逞敷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了灌侣?” 一聲冷哼從身側(cè)響起推捐,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎顶瞳,沒想到半個月后玖姑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡慨菱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年焰络,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片符喝。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡闪彼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出协饲,到底是詐尸還是另有隱情畏腕,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布茉稠,位于F島的核電站描馅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏而线。R本人自食惡果不足惜铭污,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膀篮。 院中可真熱鬧嘹狞,春花似錦、人聲如沸誓竿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筷屡。三九已至涧偷,卻和暖如春簸喂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫂丙。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工娘赴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留规哲,地道東北人跟啤。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像唉锌,于是被迫代替她去往敵國和親隅肥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容