Python 學習筆記 - socketserver原始碼剖析

ciscopuke發表於2021-09-09


前面學習的例子都是單執行緒的socket收發;如果有多個使用者同時接入,那麼除了第一個連入的,後面的都會處於掛起等待的狀態,直到當前連線的客戶端斷開為止。

透過使用socketserver,我們可以實現併發的連線。

socketserver的使用很簡單:

首先看個簡單的例子

服務端:

自己定義一個類,繼承socketserver.baserequesthandler;

然後定義一個方法 handle()

然後透過socketserver.threadingTCPServer指定套接字和自己定義的類,每次當客戶端連入的時候,會自動例項化一個物件,然後透過server_forever()不斷迴圈讀寫資料。

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author Yuan Li

import socketserver

class mysocketserver(socketserver.BaseRequestHandler):

    def handle(self):

        conn = self.request

        conn.sendall(bytes("Welcome to the Test system.", encoding='utf-8'))

        while True:

            try:

                data = conn.recv(1024)

                if len(data) == 0: break

                print("[%s] sends %s" % (self.client_address, data.decode()))

                conn.sendall(data.upper())

            except Exception:

                break

if __name__ == '__main__':

    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8009), mysocketserver)

    server.serve_forever()

客戶端:

#!/usr/bin/env python

# -*- coding:utf-8 -*-

# Author Yuan Li

import socket

ip_port = ('127.0.0.1', 8009)

s = socket.socket()

s.connect(ip_port)

data = s.recv(1024)

print(data.decode())

while True:

    send_data = input("Data>>>")

    s.send(bytes(send_data, encoding='utf-8'))

    recv_data = s.recv(1024)

    print(recv_data.decode())

上面的效果是多個客戶端可以同時連入伺服器,輸入字母,返回大寫字母。

客戶端沒啥好說的,這個和單執行緒的操作一樣;但是伺服器咋一看很混亂。我們可以透過剖析原始碼來弄清他的執行過程。

這個類的基本結構是如下所示的,我們按照順序來跑一次看看他怎麼呼叫的

wKiom1gNmjTj7TH2AAK6-Px_JOg280.png

1. 首先執行的這句話,很明顯ThredingTCPServer是一個類,點進去看看他的例項化過程

server = socketserver.ThreadingTCPServer(('127.0.0.1', 8009), mysocketserver)

2. 點著Ctrl鍵,點選這個類,PyCharm會自動開啟對應的原始碼,可以看見這個類又繼承了兩個父類ThredingMixIn和TCPServer

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

因為他的內容是pass,啥也沒做,根據繼承的順序,我們繼續往上(從左到右)找init建構函式; 

3. ThreadingMixIn裡面沒有建構函式,那就繼續往右找,TCPServer裡面倒是有建構函式,但是他又呼叫了他父類BaseServer的建構函式,順著看上去,發現他就是封裝了幾個值在裡面,注意   self.RequestHandlerClass = RequestHandlerClass把我們自己定義的類傳進去了

    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

4.接下來,在TCPServer的建構函式里面,他執行了bind,listen的操作,這個和單執行緒的操作是一樣的。到此為止,一個初始化的過程基本就完成了。

5.接下來,執行了server.serve_forever()的操作,我們看看內部是怎麼呼叫的。在這個函式里面,使用了selector的IO多路複用的技術,迴圈的讀取一個檔案的操作。接著呼叫了_handle_request_noblock()函式

  try:

            # XXX: Consider using another file descriptor or connecting to the

            # socket to wake this up instead of polling. Polling reduces our

            # responsiveness to a shutdown request and wastes cpu at all other

            # times.

            with _ServerSelector() as selector:

                selector.register(self, selectors.EVENT_READ)

                while not self.__shutdown_request:

                    ready = selector.select(poll_interval)

                    if ready:

                        self._handle_request_noblock()

                    self.service_actions()

6.每次呼叫函式的時候都記住查詢的順序,從下往上,從左往右,最後在最上面的BaseServer再次找到這個函式,這個函式里面又呼叫了 process_request函式

        """

        try:

            request, client_address = self.get_request()

        except OSError:

            return

        if self.verify_request(request, client_address):

            try:

                self.process_request(request, client_address)

一定要記住繼承的順序!!順序!!順序!!

因為baseserver自己有process_request的方法,ThreadingTCPServer也有同名的方法,當他呼叫的時候,按照順序,是執行的ThreadingTCPServer裡面的方法!!

wKioL1gNoxaThqRFAAB5Z_o7GWs657.png

wKiom1gNouDj48lSAABEODjRuN0521.png

 可以看見他開了一個多執行緒

  def process_request(self, request, client_address):

        """Start a new thread to process the request."""

        t = threading.Thread(target = self.process_request_thread,

                             args = (request, client_address))

        t.daemon = self.daemon_threads

        t.start()

在他呼叫的process_request_thread裡面,他又呼叫了finsih_request

    def process_request_thread(self, request, client_address):

        """Same as in BaseServer but as a thread.

        In addition, exception handling is done here.

        """

        try:

            self.finish_request(request, client_address)

            self.shutdown_request(request)

        except:

            self.handle_error(request, client_address)

            self.shutdown_request(request)

finish_request裡面有對我們自定義的類做了一個例項化的操作

    def finish_request(self, request, client_address):

        """Finish one request by instantiating RequestHandlerClass."""

        self.RequestHandlerClass(request, client_address, self)

因為我們自定義的類沒有建構函式,他會去父類尋找,父類裡面會嘗試執行handle()方法,這就是為什麼我們需要在自定義的類裡面定義一個同名的方法,然後把所有需要執行的內容都放在這裡。

 def __init__(self, request, client_address, server):

        self.request = request

        self.client_address = client_address

        self.server = server

        self.setup()

        try:

            self.handle()

到此,socketserver一個完整的過程就結束了

©著作權歸作者所有:來自51CTO部落格作者beanxyz的原創作品,如需轉載,請註明出處,否則將追究法律責任

pythonsocketserverPython

0


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4369/viewspace-2820060/,如需轉載,請註明出處,否則將追究法律責任。

相關文章