【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

相关文章
相关标签/搜索