扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
这期内容当中小编将会给大家带来有关python中怎么利用twisted实现TCP通讯,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
创新互联公司服务项目包括定海网站建设、定海网站制作、定海网页制作以及定海网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,定海网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到定海省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
0.写在前面
不管是服务器端还是客户端,都是通过twisted
的reactor
来启动的,所以首先就需要导入twisted.internet
包下的reactor
模块
从reactor
模块的源码中可以看出reactor
模块其实是由多个接口组成的,并且提示了具体内容需要查看twisted.internet
包下的interfaces
模块中每个接口的具体注释说明
当然从reactor
模块的注释中也说明了twisted不止可以用于TCP服务,而是提供了网络方面的API、线程、调度等功能, 但是本次的实验仅仅测试一下TCP的服务器端和客户端
reactor注释中提到的支持的接口: @see: L{IReactorCore} @see: L{IReactorTime } @see: L{IReactorProcess } @see: L{IReactorTCP } @see: L{IReactorSSL } @see: L{IReactorUDP } @see: L{IReactorMulticast } @see: L{IReactorUNIX } @see: L{IReactorUNIXDatagram } @see: L{IReactorFDSet } @see: L{IReactorThreads } @see: L{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个参数
参数名 | 意义 | 默认值 |
---|---|---|
port | 监听的端口,也就是TCP服务启动的端口 | - |
factory | 服务端的工厂类(根据注释中的提示:详情见twisted.internet 包下的protocol 模块中的ServerFactory 类) | - |
backlog | 监听队列,响应线程数 | 50 |
interface | 要绑定到的本地IPv4或IPv6地址,默认为空标示所有IPv4的地址 | ‘’ |
在设置了前两个参数以后,然后通过下面两行代码就可以启动TCP服务了
reactor.listenTCP(port, ServerFactory()) reactor.run()
但是,此时启动的服务是有问题的,当有客户端连接到该服务的时候就会报错
------ 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
根据错误提示,我们找到了问题的原因:
在客户端连接到服务器端的时候,会调用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 ##### connectedReceived 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
类的实现
------ 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) connectedReceived msg 来自服务器的响应 2019-08-10 11:57:42 from Tcp Server ('127.0.0.1', 9527)
上述就是小编为大家分享的python中怎么利用twisted实现TCP通讯了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注创新互联行业资讯频道。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流