python3網路編程之socketserver

来源:http://www.cnblogs.com/Bigtre/archive/2017/07/31/7265401.html
-Advertisement-
Play Games

本節主要是講解python3網路編程之socketserver,在上一節中我們講到了socket。由於socket無法支持多用戶和多併發,於是就有了socket server。 socket server最主要的作用就是實現併發處理。 socketserver中包含了兩種類: PS:一般情況下,所有 ...


本節主要是講解python3網路編程之socketserver,在上一節中我們講到了socket。由於socket無法支持多用戶和多併發,於是就有了socket server。

socket server最主要的作用就是實現併發處理。

socketserver中包含了兩種類:

  1. 服務類(server class):提供了許多方法:像綁定,監聽,運行等等(也就是建立連接的過程)
  2. 請求處理類(request handle class):專註於如何處理用戶所發送的數據(也就是事物邏輯)

PS:一般情況下,所有的服務,都是先建立連接,也就是建立一個服務類的實例,然後處理用戶的請求,也就是建立一個請求處理類的實例。

接下來就看一下這兩個類。

服務類

服務類有五種類型:

  1. BaseServer:不直接對外服務。
  2. TCPServer:針對TCP套接字流。
  3. UDPServer:針對UDP數據套接字。
  4. UnixStream:針對Unix套接字,不常用。
  5. UnixDatagramServer:針對Unix套接字,不常用。

他們之間的繼承關係如下:

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

 服務類的方法:

class SocketServer.BaseServer:這是模塊中的所有伺服器對象的超類。它定義了介面,如下所述,但是大多數的方法不實現,在子類中進行細化。

    BaseServer.fileno():返回伺服器監聽套接字的整數文件描述符。通常用來傳遞給select.select(), 以允許一個進程監視多個伺服器。

    BaseServer.handle_request():處理單個請求。處理順序:get_request(), verify_request(), process_request()。如果用戶提供handle()方法拋出異常,將調用伺服器的handle_error()方法。如果self.timeout內沒有請求收到, 將調用handle_timeout()並返回handle_request()。

    BaseServer.serve_forever(poll_interval=0.5): 處理請求,直到一個明確的shutdown()請求。每poll_interval秒輪詢一次shutdown。忽略self.timeout。如果你需要做周期性的任務,建議放置在其他線程。

    BaseServer.shutdown():告訴serve_forever()迴圈停止並等待其停止。python2.6版本。

    BaseServer.address_family: 地址家族,比如socket.AF_INET和socket.AF_UNIX。

    BaseServer.RequestHandlerClass:用戶提供的請求處理類,這個類為每個請求創建實例。

    BaseServer.server_address:伺服器偵聽的地址。格式根據協議家族地址的各不相同,請參閱socket模塊的文檔。

    BaseServer.socketSocket:伺服器上偵聽傳入的請求socket對象的伺服器。

伺服器類支持下麵的類變數:

    BaseServer.allow_reuse_address:伺服器是否允許地址的重用。預設為false ,並且可在子類中更改。

    BaseServer.request_queue_size

請求隊列的大小。如果單個請求需要很長的時間來處理,伺服器忙時請求被放置到隊列中,最多可以放request_queue_size個。一旦隊列已滿,來自客戶端的請求將得到 “Connection denied”錯誤。預設值通常為5 ,但可以被子類覆蓋。

    BaseServer.socket_type:伺服器使用的套接字類型; socket.SOCK_STREAM和socket.SOCK_DGRAM等。

    BaseServer.timeout:超時時間,以秒為單位,或 None表示沒有超時。如果handle_request()在timeout內沒有收到請求,將調用handle_timeout()。

下麵方法可以被子類重載,它們對伺服器對象的外部用戶沒有影響。

    BaseServer.finish_request():實際處理RequestHandlerClass發起的請求並調用其handle()方法。 常用。

    BaseServer.get_request():接受socket請求,並返回二元組包含要用於與客戶端通信的新socket對象,以及客戶端的地址。

    BaseServer.handle_error(request, client_address):如果RequestHandlerClass的handle()方法拋出異常時調用。預設操作是列印traceback到標準輸出,並繼續處理其他請求。

    BaseServer.handle_timeout():超時處理。預設對於forking伺服器是收集退出的子進程狀態,threading伺服器則什麼都不做。

    BaseServer.process_request(request, client_address) :調用finish_request()創建RequestHandlerClass的實例。如果需要,此功能可以創建新的進程或線程來處理請求,ForkingMixIn和ThreadingMixIn類做到這點。常用。

    BaseServer.server_activate():通過伺服器的構造函數來激活伺服器。預設的行為只是監聽伺服器套接字。可重載。

    BaseServer.server_bind():通過伺服器的構造函數中調用綁定socket到所需的地址。可重載。

    BaseServer.verify_request(request, client_address):返回一個布爾值,如果該值為True ,則該請求將被處理,反之請求將被拒絕。此功能可以重寫來實現對伺服器的訪問控制。預設的實現始終返回True。client_address可以限定客戶端,比如只處理指定ip區間的請求。 常用。
View Code

這個幾個服務類都是同步處理請求的:一個請求沒處理完不能處理下一個請求。要想支持非同步模型,可以利用多繼承讓server類繼承ForkingMixIn 或 ThreadingMixIn mix-in classes。

ForkingMixIn利用多進程(分叉)實現非同步。

ThreadingMixIn利用多線程實現非同步。

請求處理類

要實現一項服務,還必須派生一個handler class請求處理類,並重寫父類的handle()方法。handle方法就是用來專門是處理請求的。該模塊是通過服務類和請求處理類組合來處理請求的。

socketserver模塊提供的請求處理類有BaseRequestHandler,以及它的派生類StreamRequestHandler和DatagramRequestHandler。從名字看出可以一個處理流式套接字,一個處理數據報套接字。

請求處理類有三種方法:

  1. setup()
  2. handle()
  3. finish()

setup()  

  Called before the handle() method to perform any initialization actions required. The default implementation does nothing.

  也就是在handle()之前被調用,主要的作用就是執行處理請求之前的初始化相關的各種工作。預設不會做任何事。(如果想要讓其做一些事的話,就要程式員在自己的請求處理器中覆蓋這個方法(因為一般自定義的請求處理器都要繼承python中提供的BaseRequestHandler,ps:下文會提到的),然後往裡面添加東西即可)

 

handle() 

  This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.

  The type of self.request is different for datagram or stream services. For stream services,self.request is a socket object; for datagram services, self.request is a pair of string and socket.

  handle()的工作就是做那些所有與處理請求相關的工作。預設也不會做任何事。他有數個實例參數:self.request    self.client_address   self.server

 

finish()

  Called after the handle() method to perform any clean-up actions required. The default implementation does nothing. If setup() raises an exception, this function will not be called.

  在handle()方法之後會被調用,他的作用就是執行當處理完請求後的清理工作,預設不會做任何事

然後我們來看一下Handler的源碼:

class BaseRequestHandler:

    """Base class for request handler classes.

    This class is instantiated for each request to be handled.  The
    constructor sets the instance variables request, client_address
    and server, and then calls the handle() method.  To implement a
    specific service, all you need to do is to derive a class which
    defines a handle() method.

    The handle() method can find the request as self.request, the
    client address as self.client_address, and the server (in case it
    needs access to per-server information) as self.server.  Since a
    separate instance is created for each request, the handle() method
    can define arbitrary other instance variariables.

    """

    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


# The following two classes make it possible to use the same service
# class for stream or datagram servers.
# Each class sets up these instance variables:
# - rfile: a file object from which receives the request is read
# - wfile: a file object to which the reply is written
# When the handle() method returns, wfile is flushed properly


class StreamRequestHandler(BaseRequestHandler):

    """Define self.rfile and self.wfile for stream sockets."""

    # Default buffer sizes for rfile, wfile.
    # We default rfile to buffered because otherwise it could be
    # really slow for large data (a getc() call per byte); we make
    # wfile unbuffered because (a) often after a write() we want to
    # read and we need to flush the line; (b) big writes to unbuffered
    # files are typically optimized by stdio even when big reads
    # aren't.
    rbufsize = -1
    wbufsize = 0

    # A timeout to apply to the request socket, if not None.
    timeout = None

    # Disable nagle algorithm for this socket, if True.
    # Use only when wbufsize != 0, to avoid small packets.
    disable_nagle_algorithm = False

    def setup(self):
        self.connection = self.request
        if self.timeout is not None:
            self.connection.settimeout(self.timeout)
        if self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile = self.connection.makefile('rb', self.rbufsize)
        self.wfile = self.connection.makefile('wb', self.wbufsize)

    def finish(self):
        if not self.wfile.closed:
            try:
                self.wfile.flush()
            except socket.error:
                # An final socket error may have occurred here, such as
                # the local error ECONNABORTED.
                pass
        self.wfile.close()
        self.rfile.close()


class DatagramRequestHandler(BaseRequestHandler):

    # XXX Regrettably, I cannot get this working on Linux;
    # s.recvfrom() doesn't return a meaningful client address.

    """Define self.rfile and self.wfile for datagram sockets."""

    def setup(self):
        from io import BytesIO
        self.packet, self.socket = self.request
        self.rfile = BytesIO(self.packet)
        self.wfile = BytesIO()

    def finish(self):
        self.socket.sendto(self.wfile.getvalue(), self.client_address)
Handler源碼

從源碼中可以看出,BaseRequestHandler中的setup()/handle()/finish()什麼內容都沒有定義,而他的兩個派生類StreamRequestHandler和DatagramRequestHandler則都重寫了setup()/finish()。

因此當我們需要自己編寫socketserver程式時,只需要合理選擇StreamRequestHandler和DatagramRequestHandler之中的一個作為父類,然後自定義一個請求處理類,併在其中重寫handle()方法即可。

創建一個socketserver的步驟:

  • 自己創建一個請求處理類,並且這個類要繼承BaseRequestHandler,並且還要重寫父類裡面的handle()方法。

這個子類用來處理客戶端的請求

與客戶端所有的交互都是在handle()方法中重寫

  • 實例化一個server(如TCPServer)類,並且講Server_IP和上一步創建的子類傳給這個實例化的類(此處是TCPServer)作為參數。
  • 調用第二步實例化出來的對象的方法,這裡假定這個實例化出來的對象為server。

server.handle_request() # 只處理一個請求,處理完後退出

server.serve_forever() # 處理多個請求,永遠執行

  • 調用close()方法關閉server。

代碼示例:

伺服器端:

import socketserver
 
class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The request handler class for our server.
 
    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """
 
    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
 
    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
 
    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()
伺服器端

客戶端:

import socket
import sys
 
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
 
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
try:
    # Connect to server and send data
    sock.connect((HOST, PORT))
    sock.sendall(bytes(data + "\n", "utf-8"))
 
    # Receive data from the server and shut down
    received = str(sock.recv(1024), "utf-8")
finally:
    sock.close()
 
print("Sent:     {}".format(data))
print("Received: {}".format(received))
客戶端

但你發現,上面的代碼,依然不能同時處理多個連接。

讓你的socketserver併發起來, 必須選擇使用以下一個多併發的類

class socketserver.ForkingTCPServer

class socketserver.ForkingUDPServer

class socketserver.ThreadingTCPServer

class socketserver.ThreadingUDPServer

所以,只需要把下麵這一句

server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)

換成下麵這個,就可以多併發了,這樣,客戶端每連進一個來,伺服器端就會分配一個新的線程來處理這個客戶端的請求

server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 非同步非常重要的一點就是不會使當前線程阻塞,C#主要通過委托機制來實現非同步編程。 ...
  • 公司的核心業務合作伙伴淘寶網,最近出現泄漏用戶信息的現象,找了好久找不到根源,於是乎,淘寶那邊決定對所有敏感數據進行加密,從出口和入口都走密文,於是乎,我們的工作量就來了。 我們的一個底單資料庫,存儲了大量淘寶賣家和買家的訂單列印,申請單號,發貨,回收單號等等操作的日誌,大概有10億左右數據(自動刪 ...
  • 考慮使用靜態工廠方法代替構造器 類可以提供一個公有的靜態工廠方法(public static factory method)來返回一個類的實例。例如,Boolean類的valueOf()方法: public static Boolean valueOf(boolean b) { return (b ...
  • 1. 心得體會 1.1 線程 寫代碼時,需要至少考慮兩個問題:UI線程與子線程。 UI線程:主要處理UI線程的事情(這不是廢話嗎?) 子線程:主要做網路連接、回調、文件IO等操作。 備註:UI線程不能夠被阻塞,不然會有ANR問題。 1.2 界面 寫代碼時,不要貪圖方便在xml中用一個ViewPage ...
  • JSP製作簡單登陸界面 運行環境 eclipse+tomcat+MySQL 不知道的可以參考Jsp運行環境——Tomcat 項目列表 這裡我先把jsp文件先放在Web-INF外面訪問 代碼演示: index.jsp就好像一般網站的首頁一樣感覺,將header.jsp和footer.jsp引入其中 h ...
  • Map數據結構的使用 ...
  • 3295: [Cqoi2011]動態逆序對 Description 對於序列A,它的逆序對數定義為滿足i<j,且Ai>Aj的數對(i,j)的個數。 給1到n的一個排列,按照某種順序依次刪除m個元素。 你的任務是在每次刪除一個元素之前統計整個序列的逆序對數。 對於序列A,它的逆序對數定義為滿足i<j, ...
  • Python中的類 俗話說,物以類聚,人以群分,類是什麼,類就是一組相同屬性的集合。下麵來結合人,探討Python中類和人類的關係。 首先,我們定義一個人的類,如下: 上面代碼中,我們定義了一個人的類,人都有姓名,年齡,性別,血型等屬性,還有說話,學習,走路等方法;並且我們創建了兩個人,一男一女," ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...