如下實驗基於win10==190三、anaconda==2019.0七、python==3.六、twisted==19.7.0python
不論是服務器端仍是客戶端,都是經過twisted
的reactor
來啓動的,因此首先就須要導入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>}
根據上面reactor
模塊的註釋,發現和TCP相關的須要查看interfaces
模塊下的IReactorTCP
接口,因此接下來咱們移步至IReactorTCP
接口服務器
IReactorTCP
接口中有兩個方法listenTCP
和connectTCP
,無論從方法名仍是其說明均可以看出,前者是監聽一個端口提供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的地址 | ‘’ |
在設置了前兩個參數之後,而後經過下面兩行代碼就能夠啓動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
模塊中的Factory
類,發現類方法forProtocol
是用於建立factory
實例的,可是須要給定一個protocol
實例
很好,咱們的目標又近了一步,下面繼續研究Protocol
類
一樣在protocol
模塊中,咱們找到了Protocol
類,他繼承自BaseProtocol
類,總共有下面幾個方法
BaseProtocol.makeConnection
:用於開啓鏈接,當鏈接開啓後會回調connectionMade
方法
BaseProtocol.connectionMade
:未實現。當鏈接成功之後回調該方法
Protocol.logPrefix
:返回當前類的類名,用於日誌log
Protocol.dataReceived
:未實現。當收到請求時被調用的方法
Protocol.connectionLost
:未實現。當鏈接斷開時調用的方法
因此如今咱們只要繼承Protocol
類,寫一個本身的實現協議就能夠了,而且只須要實現父類中未實現的3個方法
爲了簡單一些,在connectionMade
和connectionLost
方法中咱們只記錄一下客戶端的鏈接信息並輸出一下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"))
好,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)
有了服務器的經驗,咱們回來看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()
根據connectTCP
方法的註釋說明,咱們直接能夠找到ClientFactory
類,類中有3個方法須要實現
ClientFactory.startedConnecting
:未實現。開啓鏈接時會調用該方法
ClientFactory.clientConnectionFailed
:未實現。鏈接失敗時會調用該方法
ClientFactory.clientConnectionLost
:未實現。鏈接斷開時會調用該方法
一樣,爲了簡單,咱們在startedConnecting
方法中只作一下日誌log的記錄,在clientConnectionFailed
和clientConnectionLost
方法中記錄入職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
父類,這裏稍微作點和服務器端不一樣的事,實現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"))
由於在建立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