如下实验基于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