【twisted】python上實現TCP通信

如下實驗基於win10==190三、anaconda==2019.0七、python==3.六、twisted==19.7.0python


0.寫在前面

不論是服務器端仍是客戶端,都是經過twistedreactor來啓動的,因此首先就須要導入twisted.internet包下的reactor模塊react

reactor模塊的源碼中能夠看出reactor模塊實際上是由多個接口組成的,而且提示了具體內容須要查看twisted.internet包下的interfaces模塊中每一個接口的具體註釋說明git

固然從reactor模塊的註釋中也說明了twisted不止能夠用於TCP服務,而是提供了網絡方面的API、線程、調度等功能, 可是本次的實驗僅僅測試一下TCP的服務器端和客戶端github

reactor註釋中提到的支持的接口:
@see: L{IReactorCore<twisted.internet.interfaces.IReactorCore>}
@see: L{IReactorTime<twisted.internet.interfaces.IReactorTime>}
@see: L{IReactorProcess<twisted.internet.interfaces.IReactorProcess>}
@see: L{IReactorTCP<twisted.internet.interfaces.IReactorTCP>}
@see: L{IReactorSSL<twisted.internet.interfaces.IReactorSSL>}
@see: L{IReactorUDP<twisted.internet.interfaces.IReactorUDP>}
@see: L{IReactorMulticast<twisted.internet.interfaces.IReactorMulticast>}
@see: L{IReactorUNIX<twisted.internet.interfaces.IReactorUNIX>}
@see: L{IReactorUNIXDatagram<twisted.internet.interfaces.IReactorUNIXDatagram>}
@see: L{IReactorFDSet<twisted.internet.interfaces.IReactorFDSet>}
@see: L{IReactorThreads<twisted.internet.interfaces.IReactorThreads>}
@see: L{IReactorPluggableResolver<twisted.internet.interfaces.IReactorPluggableResolver>}

1.TCP服務器端

·reactor反應器

根據上面reactor模塊的註釋,發現和TCP相關的須要查看interfaces模塊下的IReactorTCP接口,因此接下來咱們移步至IReactorTCP接口服務器

IReactorTCP接口中有兩個方法listenTCPconnectTCP,無論從方法名仍是其說明均可以看出,前者是監聽一個端口提供TCP服務,後者是鏈接到服務端的TCP客戶端網絡

def listenTCP(port, factory, backlog=50, interface=''):
def connectTCP(host, port, factory, timeout=30, bindAddress=None):

因此要開啓一個TCP服務端,咱們須要用到的是listenTCP方法,這個方法有4個參數socket

參數名 意義 默認值
port 監聽的端口,也就是TCP服務啓動的端口 -
factory 服務端的工廠類(根據註釋中的提示:詳情見twisted.internet包下的protocol模塊中的ServerFactory類) -
backlog 監聽隊列,響應線程數 50
interface 要綁定到的本地IPv4或IPv6地址,默認爲空標示全部IPv4的地址 ‘’

·Factory工廠類

在設置了前兩個參數之後,而後經過下面兩行代碼就能夠啓動TCP服務了tcp

reactor.listenTCP(port, ServerFactory())
reactor.run()

可是,此時啓動的服務是有問題的,當有客戶端鏈接到該服務的時候就會報錯測試

--- <exception caught here> ---
  File "D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\lib\site-packages\twisted\internet\tcp.py", line 1427, in doRead
    self._buildAddr(addr))
  File "D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\lib\site-packages\twisted\internet\protocol.py", line 140, in buildProtocol
    p = self.protocol()
builtins.TypeError: 'NoneType' object is not callable

根據錯誤提示,咱們找到了問題的緣由:ui

在客戶端鏈接到服務器端的時候,會調用Factory類(ServerFactory的父類)中的buildProtocol方法來創建通信協議(這裏能夠理解爲客戶端和服務器端之間讀寫的實現方法),其中須要調用self.protocol所指向的方法來初始化這個協議

然而此時的protocol倒是None,因此在協議的初始化階段出錯了

·Protocol協議類

再次研究一下protocol模塊中的Factory類,發現類方法forProtocol是用於建立factory實例的,可是須要給定一個protocol實例

很好,咱們的目標又近了一步,下面繼續研究Protocol

一樣在protocol模塊中,咱們找到了Protocol類,他繼承自BaseProtocol類,總共有下面幾個方法

  • BaseProtocol.makeConnection:用於開啓鏈接,當鏈接開啓後會回調connectionMade方法

  • BaseProtocol.connectionMade:未實現。當鏈接成功之後回調該方法

  • Protocol.logPrefix:返回當前類的類名,用於日誌log

  • Protocol.dataReceived:未實現。當收到請求時被調用的方法

  • Protocol.connectionLost:未實現。當鏈接斷開時調用的方法

因此如今咱們只要繼承Protocol類,寫一個本身的實現協議就能夠了,而且只須要實現父類中未實現的3個方法

爲了簡單一些,在connectionMadeconnectionLost方法中咱們只記錄一下客戶端的鏈接信息並輸出一下log,而在dataReceived方法中咱們將收到的信息打印出來,並在5s事後返回客戶端一條消息

class TcpServer(Protocol)::
    CLIENT_MAP = {}  # 用於保存客戶端的鏈接信息

    def connectionMade(self):
        addr = self.transport.client  # 獲取客戶端的鏈接信息
        print("connected", self.transport.socket)
        TcpServer.CLIENT_MAP[addr] = self

    def connectionLost(self, reason):
        addr = self.transport.client  # 獲取客戶端的鏈接信息
        if addr in TcpServer.CLIENT_MAP:
            print(addr, "Lost Connection from Tcp Server", 'Reason:', reason)
            del TcpServer.CLIENT_MAP[addr]

    def dataReceived(self, tcp_data):
        addr = self.transport.client  # 獲取客戶端的鏈接信息
        nowTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        try:
            msg = tcp_data.decode("utf-8")
            print("Received msg", msg, "from Tcp Client", addr)

            time.sleep(5)
            str = "來自服務器的響應 " + nowTime
            self.transport.write(str.encode("utf-8"))

        except BaseException as e:
            print("Comd Execute Error from", addr, "data:", tcp_data)
            str = "服務器發生異常 " + nowTime
            self.transport.write(str.encode("utf-8"))

·啓動TCP服務

好,Protocol類已經實現了,咱們用他來建立工廠實例並啓動TCP服務

port = 9527
    serverFactory = Factory.forProtocol(TcpServer)
    reactor.listenTCP(port, serverFactory)
    print("#####", "Starting TCP Server on", port, "#####")
    reactor.run()

TCP服務成功啓動,而且客戶端鏈接上來之後也沒有報錯

D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\python.exe D:/MyWorkSpaces/projects/all/sample/python/twisted/server.py
##### Starting TCP Server on 9527 #####
connected <socket.socket fd=824, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9527), raddr=('127.0.0.1', 3440)>
Received msg 你好服務器,我是客戶端 2019-08-10 11:13:09 from Tcp Client ('127.0.0.1', 3440)

2.TCP客戶端

·reactor反應器

有了服務器的經驗,咱們回來看reactor模塊中IReactorTCP接口裏的connectTCP方法,這個方法一共有5個參數

參數名 意義 默認值
host 服務器地址,IPv4或IPv6 -
prot 服務器端口 -
factory 客戶端的工廠類,(根據註釋中的提示:詳情見twisted.internet包下的protocol模塊中的ClientFactory類) -
timeout 鏈接超時時間,單位s 30
bindAddress 本地的地址,格式爲(host,port)的元祖 None

一樣也很簡單,咱們只要如下兩行代碼就能夠啓動客戶端了,可是和服務端相似,在這以前咱們也須要實現一個Factory工廠類和Protocol協議類的實例

reactor.connectTCP(host, port, factory)
reactor.run()

·Factory工廠類

根據connectTCP方法的註釋說明,咱們直接能夠找到ClientFactory類,類中有3個方法須要實現

  • ClientFactory.startedConnecting:未實現。開啓鏈接時會調用該方法

  • ClientFactory.clientConnectionFailed:未實現。鏈接失敗時會調用該方法

  • ClientFactory.clientConnectionLost:未實現。鏈接斷開時會調用該方法

一樣,爲了簡單,咱們在startedConnecting方法中只作一下日誌log的記錄,在clientConnectionFailedclientConnectionLost方法中記錄入職log之後隔30s之後重試鏈接

class TcpClientFactory(ClientFactory):

    def startedConnecting(self, connector):
        print("Starting Connecting To Tcp Server", (connector.host, connector.port))

    def clientConnectionLost(self, connector, reason):
        print("Lost Connection from Tcp Server", (connector.host, connector.port), 'Reason:', reason)
        time.sleep(30)
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        print("Failed To Connect To Tcp Server", (connector.host, connector.port), 'Reason:', reason)
        time.sleep(30)
        connector.connect()

啓動TCP客戶端,咱們發現了和第一次啓動服務端時同樣的錯誤,此次咱們有經驗了,由於少了Protocol類的實現

--- <exception caught here> ---
  File "D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\lib\site-packages\twisted\internet\selectreactor.py", line 149, in _doReadOrWrite
    why = getattr(selectable, method)()
  File "D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\lib\site-packages\twisted\internet\tcp.py", line 627, in doConnect
    self._connectDone()
  File "D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\lib\site-packages\twisted\internet\tcp.py", line 641, in _connectDone
    self.protocol = self.connector.buildProtocol(self.getPeer())
  File "D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\lib\site-packages\twisted\internet\base.py", line 1157, in buildProtocol
    return self.factory.buildProtocol(addr)
  File "D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\lib\site-packages\twisted\internet\protocol.py", line 140, in buildProtocol
    p = self.protocol()
builtins.TypeError: 'NoneType' object is not callable

·Protocol協議類

和服務器端使用的是同一個Protocol父類,這裏稍微作點和服務器端不一樣的事,實現connectionMade方法時咱們往服務器端發送一條消息

class TcpClient(Protocol):
    SERVER_MAP = {}

    def connectionMade(self):
        addr = self.transport.addr  # 獲取服務器端的鏈接信息
        print("connected", self.transport.socket)
        client_ip = addr[0]
        TcpClient.SERVER_MAP[client_ip] = self
        nowTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        str = "你好服務器,我是客戶端 " + nowTime
        self.transport.write(str.encode("utf-8"))  # 向服務器發送信息

    def connectionLost(self, reason):
        addr = self.transport.addr  # 獲取服務器端的鏈接信息
        client_ip = addr[0]
        if client_ip in TcpClient.SERVER_MAP:
            del TcpClient.SERVER_MAP[client_ip]

    def dataReceived(self, tcp_data):
        addr = self.transport.addr  # 獲取服務器端的鏈接信息
        nowTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        try:
            msg = tcp_data.decode("utf-8")
            print("Received msg", msg, "from Tcp Server", addr)

            time.sleep(5)
            str = "來自客戶端的響應 " + nowTime
            self.transport.write(str.encode("utf-8"))

        except BaseException as e:
            print("Comd Execute Error from", addr, "data:", tcp_data)
            str = "客戶端發生異常 " + nowTime
            self.transport.write(str.encode("utf-8"))

·啓動TCP客戶端

由於在建立Factory類的時候和服務器端有些不同,以前服務器端咱們是經過Factory.forProtocol方法來實例化工廠對象的,而在客戶端的時候咱們是繼承了Factory類的子類ClientFactory來實現的,因此咱們須要重寫buildProtocol方法來設置protocol實例

TcpClientFactory類中重寫buildProtocol方法:

class TcpClientFactory(ClientFactory):
    def buildProtocol(self, addr):
        print("Connected To Tcp Server", addr)
        self.protocol = TcpClient()
        return self.protocol

而後用如下代碼來啓動TCP客戶端:

host = "127.0.0.1"
    port = 9527
    reactor.connectTCP(host, port, TcpClientFactory())
    reactor.run()

TCP客戶端成功啓動,而且鏈接上服務器端之後也沒有報錯

D:\MyWorkSpaces\tools\Anaconda3\envs\tf2\python.exe D:/MyWorkSpaces/projects/all/sample/python/twisted/client.py
Starting Connecting To Tcp Server ('127.0.0.1', 9527)
Connected To Tcp Server IPv4Address(type='TCP', host='127.0.0.1', port=9527)
connected <socket.socket fd=452, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 3770), raddr=('127.0.0.1', 9527)>
Received msg 來自服務器的響應 2019-08-10 11:57:42 from Tcp Server ('127.0.0.1', 9527)

完整代碼已經上傳到github

相關文章
相關標籤/搜索