Python版本Zinx——(3)基礎(chǔ)路由模塊

??最近想研究一下關(guān)于長鏈接的相關(guān)內(nèi)容,在B站上看到了Zinx框架的視頻,是Golang語言的框架,本著更好的理解框架的內(nèi)容即彪,按照整個(gè)Zinx課程的進(jìn)度友酱,制作一個(gè)Python版本的Zinx框架。有關(guān)于Zinx框架的具體內(nèi)容肘交,可以看框架作者的介紹
??python版本的Zinx拂玻,基于Gevent 22.10.2酸些,使用協(xié)程功能。

??golang版本的Zinx項(xiàng)目檐蚜,項(xiàng)目中兩個(gè)文件夾魄懂,ziface和znet。

  • ziface主要是存放一些Zinx框架的全部模塊的抽象層接口類闯第。
  • znet模塊是zinx框架中網(wǎng)絡(luò)相關(guān)功能的實(shí)現(xiàn)市栗,所有網(wǎng)絡(luò)相關(guān)模塊都會(huì)定義在znet模塊中。
    └── zinx
    ?├── ziface
    ?│??└──
    ?└── znet
    ????├──

??python中的關(guān)鍵字沒有interface咳短,但是可以使用抽象基類(abstract base class)和第三方庫來實(shí)現(xiàn)類似于接口的功能填帽。在實(shí)際開發(fā)中,我們可以根據(jù)具體需求選擇合適的實(shí)現(xiàn)方式咙好。
??暫時(shí)使用抽象基類的形式模擬接口的實(shí)現(xiàn)篡腌。


??上一節(jié)handlerAPI寫的過于潦草了,本節(jié)就封裝一下勾效。
??首先封裝鏈接和數(shù)據(jù)嘹悼。在ziface中創(chuàng)建一個(gè)irequest。

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

from ziface.iconnection import IConnection
from abc import ABC, abstractmethod


class IRequest(ABC):
    """
    IRequest 接口:實(shí)際上是把客戶端請求的鏈接信息 和 請求的數(shù)據(jù) 包裝到了 Request里
    """
    @abstractmethod
    def GetConnection(self) -> IConnection:
        """
        獲取請求連接信息
        :return:
        """
        pass

    @abstractmethod
    def GetData(self) -> bytes:
        """
        獲取請求消息的數(shù)據(jù)
        :return:
        """
        pass

??在znet中創(chuàng)建request.py层宫,做IRequest的實(shí)現(xiàn)類杨伙。

# -*- coding: utf-8 -*-
from ziface.irequest import IRequest
from ziface.iconnection import IConnection


class Request(IRequest):
    def __init__(self, conn: IConnection, data: bytes):
        """
        :param conn: 已經(jīng)和客戶端建立好的 鏈接
        :param msg: 客戶端請求的數(shù)據(jù)
        """
        self.Conn: IConnection = conn
        self.Data: bytes = data

    def GetConnection(self) -> IConnection:
        """
        獲取請求連接信息
        :return:
        """
        return self.Conn

    def GetData(self) -> bytes:
        """
        獲取請求消息的數(shù)據(jù)
        :return:
        """
        return self.Data


def NewRequest(conn: IConnection, data: bytes) -> IRequest:
    req = Request(conn, data)
    return req

??鏈接和數(shù)據(jù)封裝好了之后,做一下路由模塊的基礎(chǔ)封裝萌腿。在ziface中創(chuàng)建一個(gè)irouter限匣。

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

from ziface.irequest import IRequest
from abc import ABC, abstractmethod


class IRouter(ABC):
    """
    路由接口, 這里面路由是 使用框架者給該鏈接自定的 處理業(yè)務(wù)方法
    路由里的IRequest 則包含用該鏈接的鏈接信息和該鏈接的請求數(shù)據(jù)信息
    """
    @abstractmethod
    def PreHandle(self, request: IRequest):
        """
        在處理conn業(yè)務(wù)之前的鉤子方法
        :param request:
        :return:
        """
        pass

    @abstractmethod
    def Handle(self, request: IRequest):
        """
        處理conn業(yè)務(wù)的方法
        :param request:
        :return:
        """
        pass

    @abstractmethod
    def PostHandle(self, request: IRequest):
        """
        處理conn業(yè)務(wù)之后的鉤子方法
        :param request:
        :return:
        """
        pass

??在znet中創(chuàng)建router.py毁菱,做IRouter的實(shí)現(xiàn)類米死,這個(gè)需要做成一個(gè)基類锌历,用戶自定義的路由模塊直接繼承基類就行了。

# -*- coding: utf-8 -*-
from ziface.irequest import IRequest
from ziface.irouter import IRouter


class BaseRouter(IRouter):
    """
    實(shí)現(xiàn)router時(shí)峦筒,先嵌入這個(gè)基類辩涝,然后根據(jù)需要對這個(gè)基類的方法進(jìn)行重寫
    這里之所以BaseRouter的方法都為空,是因?yàn)橛械腞outer不希望有PreHandle或PostHandle
    所以Router全部繼承BaseRouter的好處是勘天,不需要實(shí)現(xiàn)PreHandle和PostHandle也可以實(shí)例化
    """
    def __init__(self):
        pass

    def PreHandle(self, request: IRequest):
        """
        在處理conn業(yè)務(wù)之前的鉤子方法
        :param request:
        :return:
        """
        pass

    def Handle(self, request: IRequest):
        """
        處理conn業(yè)務(wù)的方法
        :param request:
        :return:
        """
        pass

    def PostHandle(self, request: IRequest):
        """
        處理conn業(yè)務(wù)之后的鉤子方法
        :param request:
        :return:
        """
        pass

??Request模塊與Router模塊封裝好了之后,在Server和Connection中集成捉邢。
??在IServer新增一個(gè)添加路由的方法脯丝。

# -*- coding: utf-8 -*-
from ziface.irouter import IRouter
from abc import ABC, abstractmethod


class IServer(ABC):
    """
    定義服務(wù)器接口
    """
    @abstractmethod
    def Start(self):
        """
        啟動(dòng)服務(wù)器方法
        :return:
        """
        pass

    @abstractmethod
    def Stop(self):
        """
         停止服務(wù)器方法
         :return:
         """
        pass

    @abstractmethod
    def Serve(self):
        """
        開啟業(yè)務(wù)服務(wù)方法
        :return:
        """
        pass

    @abstractmethod
    def AddRouter(self, router: IRouter):
        """
        添加路由
        :param router:
        :return:
        """
        pass

??將Connection中的handlerAPI刪除,添加一個(gè)Router伏伐,并且在StartReader中添加響應(yīng)的處理Router的代碼宠进。

# -*- coding: utf-8 -*-
import socket
import gevent
from ziface.iconnection import IConnection
from ziface.irequest import IRequest
from ziface.irouter import IRouter
from znet.request import NewRequest

# 存一下協(xié)程
GlobalGevents: list = []


class Connection(IConnection):
    def __init__(self, conn: socket.socket, connID: int, remote_addr: tuple, router:IRouter):
        self.Conn: socket.socket = conn  # 當(dāng)前鏈接的socket TCP套接字
        self.ConnID: int = connID  # 鏈接的ID
        # self.HandlerAPI = handlerAPI  # 當(dāng)前鏈接綁定的業(yè)務(wù)處理方法的API
        self.is_closed: bool = False  # 鏈接狀態(tài)
        self.Remote_Addr: tuple = remote_addr
        self.Router: IRouter = router

    def Start(self):
        """
        啟動(dòng)鏈接 讓當(dāng)前的鏈接準(zhǔn)備開始工作
        :return:
        """
        print("鏈接開啟,ID=", self.ConnID)
        # 開啟讀業(yè)務(wù)
        g1 = gevent.spawn(self.StartReader)
        GlobalGevents.append(g1)

    def StartReader(self):
        """
        處理讀業(yè)務(wù)
        :return:
        """
        print("開啟讀業(yè)務(wù)")
        while True:
            try:
                # 讀取客戶端的Msg head
                read_buf = self.Conn.recv(512)
                if len(read_buf) == 0:
                    # head_data 長度為0表示客戶端已經(jīng)退出
                    break
                # self.HandlerAPI(self.Conn, read_buf)
                req = NewRequest(self, read_buf)
                g2 = gevent.spawn(self.RunHandler(req))
                GlobalGevents.append(g2)
            except Exception as e:
                print("讀取數(shù)據(jù)異常 ", e)
                break
        print("讀業(yè)務(wù)關(guān)閉藐翎,ID", self.ConnID)
        self.Stop()

    def RunHandler(self, request: IRequest):
        self.Router.PreHandle(request)
        self.Router.Handle(request)
        self.Router.PostHandle(request)

    def Stop(self):
        """
        停止鏈接 結(jié)束當(dāng)前鏈接的工作
        :return:
        """
        print("鏈接關(guān)閉材蹬,ID=", self.ConnID)
        if self.is_closed:
            return
        self.is_closed = True
        # 關(guān)閉socket鏈接,回收資源
        self.Conn.close()

    def GetTCPConnection(self) -> socket.socket:
        """
        獲取當(dāng)前鏈接綁定的socket conn
        :return:
        """
        return self.Conn

    def GetConnID(self) -> int:
        """
        獲取當(dāng)前鏈接模塊的鏈接ID
        :return:
        """
        return self.ConnID

    def GetRemoteAddr(self) -> tuple:
        """
        獲取遠(yuǎn)程客戶端的TCP狀態(tài) IP Port
        :return:
        """
        return self.Remote_Addr


def NewConnection(conn: socket.socket, connID: int, remote_addr: tuple, router: IRouter) -> IConnection:
    c = Connection(conn, connID, remote_addr, router)
    return c

??將Server中的CallBackToClient刪除吝镣,添加一個(gè)Router堤器,并且在InitTCP中添加響應(yīng)的處理Router的代碼。

# -*- coding: utf-8 -*-
from typing import Optional

from ziface.irouter import IRouter
from ziface.iserver import IServer
from znet.connection import NewConnection
from gevent import monkey
import time
import socket
import gevent
import signal
import sys


# 把內(nèi)置阻塞的庫替換成非阻塞的socket
monkey.patch_socket()
# 存一下協(xié)程
GlobalGevents: list = []


class Server(IServer):
    def __init__(self, name: str, ip: str, port: int,
                 family: socket.AddressFamily = socket.AF_INET,
                 socket_kind: socket.SocketKind = socket.SOCK_STREAM):
        self.name: str = name
        self.family: socket.AddressFamily = family
        self.socket_kind: socket.SocketKind = socket_kind
        self.ip: str = ip
        self.port: int = port
        self.Router: Optional[IRouter] = None

    def Start(self):
        """
        啟動(dòng)服務(wù)器方法
        :return:
        """
        print("[啟動(dòng)] 服務(wù)監(jiān)聽末贾,IP地址:%s, 端口:%s闸溃,已經(jīng)啟動(dòng)\n" % (self.ip, self.port))
        g1 = gevent.spawn(self.InitTCP)
        GlobalGevents.append(g1)

    def InitTCP(self):
        """
        初始化TCP鏈接
        :return:
        """
        # 1.獲取一個(gè)TCP的Addr
        with socket.socket(self.family, self.socket_kind) as tcp:
            # 2.監(jiān)聽服務(wù)器的地址
            tcp.bind((self.ip, self.port))
            tcp.listen(128)
            print("[啟動(dòng)] %s服務(wù)啟動(dòng)成功,監(jiān)聽中......" % self.name)
            # 3.阻塞的等待客戶端鏈接拱撵,處理客戶端鏈接業(yè)務(wù)(讀寫)辉川。
            cid: int = 0
            while True:
                print("開啟接收")
                remote_socket, remote_addr = tcp.accept()
                dealConn = NewConnection(remote_socket, cid, remote_addr, self.Router)
                cid += 1
                g2 = gevent.spawn(dealConn.Start())
                GlobalGevents.append(g2)

    def Stop(self):
        """
        停止服務(wù)器方法
        :return:
        """
        print("服務(wù)停止")

    def Serve(self):
        """
        開啟業(yè)務(wù)服務(wù)方法
        :return:
        """
        self.Start()
        gevent.joinall(GlobalGevents)
        # 阻塞一下
        while True:
            time.sleep(10)

    def AddRouter(self, router: IRouter):
        """
        添加路由
        :param router:
        :return:
        """
        self.Router = router
        print("路由添加成功")


# def CallBackToClient(conn: socket.socket, data: bytes):
#     print("回顯功能")
#     try:
#         conn.send(data)
#     except Exception as e:
#         print(e)
#         raise Exception("回顯功能異常")


# 創(chuàng)建一個(gè)全局的server
m_server: Optional[IServer] = None


# 獲取server
def NewServer() -> IServer:
    global m_server
    if m_server:
        return m_server
    m_server = Server("測試", "0.0.0.0", 8986)
    return m_server


# 接收停止信號(hào),要不然報(bào)錯(cuò)看著煩
def signal_handler(signal, frame):
    global m_server
    m_server.Stop()
    print("程序已退出")
    sys.exit(0)


# 接收停止信號(hào) ctrl+c
signal.signal(signal.SIGINT, signal_handler)

??創(chuàng)建一個(gè)demo文件夾拴测,用于寫測試方法乓旗。demo中創(chuàng)建router文件夾,表明是測試Router集索。router文件夾中屿愚,創(chuàng)建一個(gè)root.py,寫上測試函數(shù)抄谐。

# -*- coding: utf-8 -*-
import sys

sys.path.append("../../")
from znet.server import NewServer
from znet.router import BaseRouter
from ziface.irequest import IRequest


class PingRouter(BaseRouter):
    """
    用戶自定義路由
    """

    def __init__(self):
        super().__init__()

    def PreHandle(self, request: IRequest):
        """
        在處理conn業(yè)務(wù)之前的鉤子方法
        :param request:
        :return:
        """
        try:
            print("調(diào)用PreHandle")
            request.GetConnection().GetTCPConnection().send("ping前\n".encode("GB2312"))
        except Exception as e:
            print("ping前異常", e)

    def Handle(self, request: IRequest):
        """
        處理conn業(yè)務(wù)的方法
        :param request:
        :return:
        """
        try:
            print("調(diào)用Handle")
            request.GetConnection().GetTCPConnection().send("ping\n".encode("GB2312"))
            request.GetConnection().GetTCPConnection().send(request.GetData())
        except Exception as e:
            print("ping異常", e)

    def PostHandle(self, request: IRequest):
        """
        處理conn業(yè)務(wù)之后的鉤子方法
        :param request:
        :return:
        """
        try:
            print("調(diào)用PostHandle")
            request.GetConnection().GetTCPConnection().send("ping后\n".encode("GB2312"))
        except Exception as e:
            print("ping后異常", e)


if __name__ == '__main__':
    server = NewServer()
    server.AddRouter(PingRouter())
    server.Serve()

??服務(wù)接口做完了渺鹦。在cmd中輸入命令,回車

nc 127.0.0.1 8986

??輸入幾個(gè)字符蛹含,回車鍵發(fā)送毅厚,此時(shí)封裝好的Router和Request已經(jīng)集成到Zinx中,用戶自定義的PingRouter回顯功能可以正常運(yùn)行浦箱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吸耿,一起剝皮案震驚了整個(gè)濱河市祠锣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咽安,老刑警劉巖伴网,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妆棒,居然都是意外死亡澡腾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門糕珊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來动分,“玉大人,你說我怎么就攤上這事红选±焦” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵喇肋,是天一觀的道長坟乾。 經(jīng)常有香客問我,道長蝶防,這世上最難降的妖魔是什么甚侣? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮慧脱,結(jié)果婚禮上渺绒,老公的妹妹穿的比我還像新娘。我一直安慰自己菱鸥,他們只是感情好宗兼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著氮采,像睡著了一般殷绍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鹊漠,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天主到,我揣著相機(jī)與錄音,去河邊找鬼躯概。 笑死登钥,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娶靡。 我是一名探鬼主播牧牢,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了塔鳍?” 一聲冷哼從身側(cè)響起伯铣,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轮纫,沒想到半個(gè)月后腔寡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掌唾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年放前,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糯彬。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡犀斋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出情连,到底是詐尸還是另有隱情,我是刑警寧澤览效,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布却舀,位于F島的核電站,受9級(jí)特大地震影響锤灿,放射性物質(zhì)發(fā)生泄漏挽拔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一但校、第九天 我趴在偏房一處隱蔽的房頂上張望螃诅。 院中可真熱鬧,春花似錦状囱、人聲如沸术裸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袭艺。三九已至,卻和暖如春叨粘,著一層夾襖步出監(jiān)牢的瞬間猾编,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工升敲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留答倡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓驴党,卻偏偏與公主長得像瘪撇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

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