【问题标题】:How to reconnect a socket on asyncio?如何在 asyncio 上重新连接套接字?
【发布时间】:2014-09-23 14:59:26
【问题描述】:

我想在 app.py 上使用 asyncio 创建两个协议(TcpClient 和 UdpServer),其中 TcpClient 将与 server.py 和 UdpServer 作为 UDP 服务器建立持久连接:

我需要什么:
a) 表示两种协议进行通信:互相调用方法。这仅适用于第一个连接。如果 TcpClient 重新连接,它不能再次发送字符串“send to tcp”。来自 UdpServer。我与print(self) 核对,TcpClient 创建了一个新实例,旧实例仍然存在但没有连接,但我不知道如何重构它。我认为我以错误的方式使用 asyncio。
b) 当 TcpClient 与 server.py 断开连接时,等待 5s 并尝试重新连接,以此类推。我尝试使用 call_later() 的 asyncio 来做到这一点,但我认为有一种本地方法可以做到这一点,而不是技巧。
c)当我启动 app.py 时,如果 TcpClient 无法连接,我想在 5 秒后尝试重新连接,依此类推。我不知道该怎么做。

这里是我对 app.py server.py 的示例测试。 server.py 仅用于测试 - 这将是另一种语言。

只是说说我的尝试:
1) 当我启动 app.py 并且 server.py 关闭时,app.py 不要重试。
2)当app.py连接到server.py并且服务器关闭并快速启动时,TcpClient重新连接,但我不能更多地在新实例上连接其他方法并发送字符串“send to tcp”。到 server.py,只是旧的,没有更多的连接。
3) 如果我使用asyncio.async() 而不是run_until_complete() 我不能从其他协议调用方法。

我把 app.py 和 server.py 放在这里,所以你可以复制并运行到测试。

我使用ncat localhost 9000 -u -v 发送字符串“send to tcp.”。该字符串需要打印在 UdpServer 类上并传递给 TcpClient 类的方法 send_data_to_tcp,该方法会将字符串发送到 server.py。

我正在使用 Python 3.4.0。

非常感谢。

app.py:

import asyncio

#TCP client
class TcpClient(asyncio.Protocol):
    message = 'Testing'

    def connection_made(self, transport):
        self.transport = transport
        self.transport.write(self.message.encode())
        print('data sent: {}'.format(self.message))
        server_udp[1].tcp_client_connected()


    def data_received(self, data):
        self.data = format(data.decode())
        print('data received: {}'.format(data.decode()))
        if self.data == 'Testing':
            server_udp[1].send_data_to_udp(self.data)

    def send_data_to_tcp(self, data):
        self.transport.write(data.encode())

    def connection_lost(self, exc):
        msg = 'Connection lost with the server...'
        info = self.transport.get_extra_info('peername')
        server_udp[1].tcp_client_disconnected(msg, info)


#UDP Server
class UdpServer(asyncio.DatagramProtocol):

    CLIENT_TCP_TIMEOUT = 5.0

    def __init__(self):
        self.client_tcp_timeout = None

    def connection_made(self, transport):
        print('start', transport)
        self.transport = transport

    def datagram_received(self, data, addr):
        self.data = data.strip()
        self.data = self.data.decode()
        print('Data received:', self.data, addr)
        if self.data == 'send to tcp.':
            client_tcp[1].send_data_to_tcp(self.data)

    def connection_lost(self, exc):
        print('stop', exc)

    def send_data_to_udp(self, data):
        print('Receiving on UDPServer Class: ', (data))

    def connect_client_tcp(self):
        coro = loop.create_connection(TcpClient, 'localhost', 8000)
        #client_tcp = loop.run_until_complete(coro)
        client_tcp = asyncio.async(coro)

    def tcp_client_disconnected(self, data, info):
        print(data)
        self.client_tcp_info = info
        self.client_tcp_timeout = asyncio.get_event_loop().call_later(self.CLIENT_TCP_TIMEOUT, self.connect_client_tcp)

    def tcp_client_connected(self):
        if self.client_tcp_timeout:
            self.client_tcp_timeout.cancel()
            print('call_later cancel.')

loop = asyncio.get_event_loop()

#UDP Server
coro = loop.create_datagram_endpoint(UdpServer, local_addr=('localhost', 9000)) 
#server_udp = asyncio.Task(coro)
server_udp = loop.run_until_complete(coro)


#TCP client
coro = loop.create_connection(TcpClient, 'localhost', 8000)
#client_tcp = asyncio.async(coro)
client_tcp = loop.run_until_complete(coro)

loop.run_forever()

server.py:

import asyncio

class EchoServer(asyncio.Protocol):
    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        print('data received: {}'.format(data.decode()))
        self.transport.write(data)

        # close the socket
        #self.transport.close()

    #def connection_lost(self):
    #    print('server closed the connection')



loop = asyncio.get_event_loop()
coro = loop.create_server(EchoServer, 'localhost', 8000)
server = loop.run_until_complete(coro)
print(server)
print(dir(server))
print(dir(server.sockets))

print('serving on {}'.format(server.sockets[0].getsockname()))

try:
    loop.run_forever()
except KeyboardInterrupt:
    print("exit")
finally:
    server.close()
    loop.close()

【问题讨论】:

    标签: python sockets client-server python-3.4 python-asyncio


    【解决方案1】:

    您真的只需要一些小修复。首先,我写了一个协程来处理连接重试:

    @asyncio.coroutine
    def do_connect():
        global tcp_server  # Make sure we use the global tcp_server
        while True:
            try:
                tcp_server = yield from loop.create_connection(TcpClient, 
                                                               'localhost', 8000)
            except OSError:
                print("Server not up retrying in 5 seconds...")
                yield from asyncio.sleep(5)
            else:
                break
    

    然后我们用它来启动一切:

    loop = asyncio.get_event_loop()
    
    #UDP Server
    coro = loop.create_datagram_endpoint(UdpServer, local_addr=('localhost', 9000)) 
    server_udp = loop.run_until_complete(coro)
    
    #TCP client
    loop.run_until_complete(do_connect())
    
    loop.run_forever()
    

    下一部分是在 app.py 启动后处理服务器宕机/恢复。我们需要修复 tcp_client_disconnected 和 connect_client_tcp 才能正确处理:

    def connect_client_tcp(self):
        global client_tcp
        task = asyncio.async(do_connect())
        def cb(result):
            client_tcp = result
        task.add_done_callback(cb)
    
    def tcp_client_disconnected(self, data, info):
        print(data)
        self.client_tcp_info = info
        self.client_tcp_timeout = loop.call_later(self.CLIENT_TCP_TIMEOUT, self.connect_client_tcp)
    

    有趣的是connect_client_tcp。您的原始版本有两个问题:

    1. 您将client_tcp 直接分配给asyncio.async(coro) 的结果,这意味着client_tcp 被分配给asyncio.Task。那不是你想要的。您希望将 client_tcp 分配给已完成的 asyncio.Task 的结果。我们通过使用task.add_done_callback 将client_tcp 分配给Task 的结果来实现这一点。

    2. 您忘记了方法顶部的global client_tcp。没有它,您只是创建了一个名为 client_tcp 的局部变量,该变量在 connect_client_tcp 的末尾被丢弃。

    一旦这些问题得到解决,我就可以随时运行app.py、启动/停止serv.py,但是当所有三个组件都运行时,始终可以看到从ncat 正确传递到serv.py 的所有消息在一起。

    这是完整的app.py,方便复制/粘贴:

    import asyncio
    
    #TCP client
    class TcpClient(asyncio.Protocol):
        message = 'Testing'
    
        def connection_made(self, transport):
            self.transport = transport
            self.transport.write(self.message.encode())
            print('data sent: {}'.format(self.message))
            server_udp[1].tcp_client_connected()
    
    
        def data_received(self, data):
            self.data = format(data.decode())
            print('data received: {}'.format(data.decode()))
            if self.data == 'Testing':
                server_udp[1].send_data_to_udp(self.data)
    
        def send_data_to_tcp(self, data):
            self.transport.write(data.encode())
    
        def connection_lost(self, exc):
            msg = 'Connection lost with the server...'
            info = self.transport.get_extra_info('peername')
            server_udp[1].tcp_client_disconnected(msg, info)
    
    
    #UDP Server
    class UdpServer(asyncio.DatagramProtocol):
    
        CLIENT_TCP_TIMEOUT = 5.0
    
        def __init__(self):
            self.client_tcp_timeout = None
    
        def connection_made(self, transport):
            print('start', transport)
            self.transport = transport
    
        def datagram_received(self, data, addr):
            self.data = data.strip()
            self.data = self.data.decode()
            print('Data received:', self.data, addr)
            if self.data == 'send to tcp.':
                client_tcp[1].send_data_to_tcp(self.data)
    
        def connection_lost(self, exc):
            print('stop', exc)
    
        def send_data_to_udp(self, data):
            print('Receiving on UDPServer Class: ', (data))
    
        def connect_client_tcp(self):
            global client_tcp
            coro = loop.create_connection(TcpClient, 'localhost', 8000)
            task = asyncio.async(do_connect())
            def cb(result):
                client_tcp = result
            task.add_done_callback(cb)
    
        def tcp_client_disconnected(self, data, info):
            print(data)
            self.client_tcp_info = info
            self.client_tcp_timeout = loop.call_later(self.CLIENT_TCP_TIMEOUT, self.connect_client_tcp)
    
        def tcp_client_connected(self):
            if self.client_tcp_timeout:
                self.client_tcp_timeout.cancel()
                print('call_later cancel.')
    
    @asyncio.coroutine
    def do_connect():
        global client_tcp
        while True:
            try:
                client_tcp = yield from loop.create_connection(TcpClient, 'localhost', 8000)
            except OSError:
                print("Server not up retrying in 5 seconds...")
                yield from asyncio.sleep(1)
            else:
                break
    
    loop = asyncio.get_event_loop()
    
    #UDP Server
    coro = loop.create_datagram_endpoint(UdpServer, local_addr=('localhost', 9000))
    server_udp = loop.run_until_complete(coro)
    
    #TCP client
    loop.run_until_complete(do_connect())
    
    loop.run_forever()
    

    【讨论】:

    • 非常感谢,完美! :)
    • 有没有办法用新语法做到这一点?当我这样做时,我得到生成器不可调用。
    猜你喜欢
    • 1970-01-01
    • 2015-04-13
    • 2019-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-10
    相关资源
    最近更新 更多