1. 程式人生 > >我的Python成長之路--Day41-知識點補充(socketserver)

我的Python成長之路--Day41-知識點補充(socketserver)

之前我們學了socket的使用,在我們建立socket物件的時候,不管是基於TCP還是UDP都有一點繁瑣,特別是TCP,可能有很多人記不住流程,那麼我們今天來給大家介紹一下socketserver

Socketserver

1、什麼是socketserver:
socketserver是專門針對socket服務端所做的一個更近一步封裝的模組,要注意它只是針對socket服務端哦,對於客戶端是沒有任何處理的,服務端還是按照之前socket的流程來寫就可以了

2、為什麼要使用socketserver
這就很明顯了,使用socketserver可以給我們簡化程式碼,同時socketserver還幫我們封裝了多執行緒的功能,我們不需要其他設定,就可以實現多個客戶端的併發

3.socketserver的介紹:
拿基於TCP的套接字來說,關鍵就是兩個迴圈,一個連線迴圈,一個通訊迴圈

socketserver模組中分為兩大類:server類(解決連線問題)  和request類(解決通訊問題)

下面我們通過圖片來了解一下這兩大類中都具體包含哪些類:
server類:

request類:

下面我們來介紹一下socketserver的使用方法:
我們在使用的過程中主要使用:ThreadingTCPServer  和 ThreadingUDPPServer  這兩個類分別應用於TCP和UDP的服務端來使得我們的程式碼更精簡
我們要注意forkingUDP和 forkingTCP 這兩個類在windows下是無法使用的,因為fork是unix系統上開程序的方法

在使用的時候必須要自定義一個類,這個類要繼承socketserver.BaseRequestHandler,並且將該類作為引數傳入ThreadingTCPServer或者ThreadingUDPServer例項化時後邊的第二個引數中

自定義類的時候類的下邊必須包含handle函式:

使用ThreadingTCPServer或者ThreadingUDPServer例項化的時候,第一個引數要傳入伺服器地址和自定義的資料處理類
在這一步的時候,在初始化的時候建立了socket物件,例項化的時候使用了socket  OPP多執行緒技術

最後例項化出物件之後直接呼叫物件下面的server_forever方法,這個方法下面幫我們封裝了很多功能
呼叫server_forever()方法的時候:其實是將產生的socket物件註冊到select(多路複用),由select檢測註冊的socket有無可用的諒解或者資料,有的話select中返回一個ready,如果True則可以處理

_handle_request_noblock內部建立了Myhandler的示例,呼叫了handler函式

4、什麼時候用socketserver

正常開發中,如果併發量不大,就可以使用socketserver,如果併發量較大就使用協程

下面是使用socketserver做的基本的TCP和UDP套接字,並且在其中有一定的註釋:
使用socketserver做TCP的服務端:
 

# -*- coding:'utf-8' 
# Date:2018/11/15 9:37
# Tool:PyCharm
# Python_version:3.6.6
__author__ = "Onion"
import socketserver
from threading import current_thread
'''
列印了那麼多是為了清楚每一個物件是什麼,在實際使用過程中完全可以去掉
'''

class Myhandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(self)
        print(self.server)  # 獲取封裝的伺服器物件
        print(self.client_address)  # 客戶端地址
        print(self.request)     # 獲取客戶端的socket物件
        print(current_thread().name)
        while True:
            try:
                data = self.request.recv(1024)
                self.request.send(data.upper())
            except ConnectionResetError:
                break


server = socketserver.ThreadingTCPServer(('localhost', 8088), Myhandler)
server.serve_forever()

使用socketserver做UDP的服務端:
 

# -*- coding:'utf-8' 
# Date:2018/11/16 8:51
# Tool:PyCharm
# Python_version:3.6.6
__author__ = "Onion"
import socketserver


class Myhandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(self)
        print(self.server)  # 獲取封裝的伺服器物件
        print(self.client_address)  # 客戶端地址
        print(self.request)  # 是一個元祖  包含收到的資料和伺服器端的socket

        data = self.request[0]
        print(data.decode("utf-8"))
        self.request[1].sendto(b'hello', self.client_address)


server = socketserver.ThreadingUDPServer(("localhost", 8087), Myhandler)
server.serve_forever()

我們要注意這兩個核心類使用的區別:
ThreadingTCPServer
    handler 在連線成功時執行
    self.request 是客戶端的socket物件  

ThreadingUDPServer
    handler 接收到資料時執行
    self.request  資料和伺服器端的socket物件

下面是一些類的繼承關係圖:(瞭解知識)

這些繼承關係是怎麼知道的呢,
以下述程式碼為例,分析socketserver原始碼:

ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
ftpserver.serve_forever()
 

查詢屬性的順序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

  1. 例項化得到ftpserver,先找類ThreadingTCPServer的__init__,在TCPServer中找到,進而執行server_bind,server_active

  2. 找ftpserver下的serve_forever,在BaseServer中找到,進而執行self._handle_request_noblock(),該方法同樣是在BaseServer中

  3. 執行self._handle_request_noblock()進而執行request, client_address = self.get_request()(就是TCPServer中的self.socket.accept()),然後執行self.process_request(request, client_address)

  4. 在ThreadingMixIn中找到process_request,開啟多執行緒應對併發,進而執行process_request_thread,執行self.finish_request(request, client_address)

  5. 上述四部分完成了連結迴圈,本部分開始進入處理通訊部分,在BaseServer中找到finish_request,觸發我們自己定義的類的例項化,去找__init__方法,而我們自己定義的類沒有該方法,則去它的父類也就是BaseRequestHandler中找....

原始碼分析總結:

基於tcp的socketserver我們自己定義的類中的

  1.   self.server即套接字物件

  2.   self.request即一個連結

  3.   self.client_address即客戶端地址

基於udp的socketserver我們自己定義的類中的

  1.   self.request是一個元組(第一個元素是客戶端發來的資料,第二部分是服務端的udp套接字物件),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)

  2.   self.client_address即客戶端地址