温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

python中怎么利用twisted实现TCP通讯

发布时间:2021-07-02 15:19:45 来源:亿速云 阅读:616 作者:Leah 栏目:大数据

这期内容当中小编将会给大家带来有关python中怎么利用twisted实现TCP通讯,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

0.写在前面

不管是服务器端还是客户端,都是通过twistedreactor来启动的,所以首先就需要导入twisted.internet包下的reactor模块

reactor模块的源码中可以看出reactor模块其实是由多个接口组成的,并且提示了具体内容需要查看twisted.internet包下的interfaces模块中每个接口的具体注释说明

当然从reactor模块的注释中也说明了twisted不止可以用于TCP服务,而是提供了网络方面的API、线程、调度等功能, 但是本次的实验仅仅测试一下TCP的服务器端和客户端

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个参数

参数名意义默认值
port监听的端口,也就是TCP服务启动的端口-
factory服务端的工厂类(根据注释中的提示:详情见twisted.internet包下的protocol模块中的ServerFactory类)-
backlog监听队列,响应线程数50
interface要绑定到的本地IPv4或IPv6地址,默认为空标示所有IPv4的地址‘’

·Factory工厂类

在设置了前两个参数以后,然后通过下面两行代码就可以启动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

根据错误提示,我们找到了问题的原因:

在客户端连接到服务器端的时候,会调用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连接超时时间,单位s30
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)

上述就是小编为大家分享的python中怎么利用twisted实现TCP通讯了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI