【问题标题】:async client: `connection_lost` not called, results in ResourceWarning异步客户端:未调用“connection_lost”,导致 ResourceWarning
【发布时间】:2017-01-17 11:06:14
【问题描述】:

我正在使用 asyncio 客户端连接然后断开与服务器的连接。

  • 如果我连接到同一台计算机上的服务器程序,连接正常关闭。 添加:当我开始向连接写入数据时,此连接也开始给出警告有时。请参阅下面的第二个代码版本。

  • 如果我连接到本地网络上的设备,我会得到ResourceWarning 用于未关闭的传输。

如何正确关闭连接?

我在 Windows 7(64 位)上使用 Python 3.6.0(32 位)。

第一次尝试

相关代码:

import asyncio
import logging
import warnings

class ClientConnection(asyncio.Protocol):
    def connection_made(self, transport):
        self.transport = transport
        transport.write_eof()  # Send EOF to close connection

    def connection_lost(self, exception):
        self.transport.close()
        super().connection_lost(exception)

def main():
    logging.basicConfig(level=logging.DEBUG)
    eventLoop = asyncio.get_event_loop()
    eventLoop.set_debug(True)
    warnings.simplefilter('always', ResourceWarning)  # enables ResourceWarning
    #co = eventLoop.create_connection(ClientConnection, '127.0.0.1', 7001)     # Works without warning
    co = eventLoop.create_connection(ClientConnection, '192.168.10.66', 7001)  # Gives warning
    try:
        eventLoop.run_until_complete(co)
    finally:
        eventLoop.close()

if __name__ == "__main__":
    main()

控制台输出:

DEBUG:asyncio:Using selector: SelectSelector
DEBUG:asyncio:connect <socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6> to ('19
2.168.10.66', 7001)
DEBUG:asyncio:poll took 0.000 ms: 1 events
DEBUG:asyncio:<socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('192.168
.10.62', 64587), raddr=('192.168.10.66', 7001)> connected to 192.168.10.66:7001: (<_SelectorSocketTransport fd=240 read=
polling write=<idle, bufsize=0>>, <__main__.ClientConnection object at 0x005EBD90>)
DEBUG:asyncio:Close <_WindowsSelectorEventLoop running=False closed=False debug=True>
sys:1: ResourceWarning: unclosed <socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto
=6, laddr=('192.168.10.62', 64587), raddr=('192.168.10.66', 7001)>
C:\Program Files (x86)\Python36-32\lib\asyncio\selector_events.py:631: ResourceWarning: unclosed transport <_SelectorSoc
ketTransport fd=240>
  source=self)

第二次尝试

我对代码做了以下更改:

  • connection_lost 中删除了transport.close()
  • 向连接写入一些数据
  • 添加了data_receivedeof_received 回调
  • 添加了更多调试日志

观察:

  • 我尝试将transport.close() 添加到connection_made,但它总是会导致OSError: [WinError 10038]注意:这可能是另一个问题,所以让我们暂时忽略它,假设我不会这样做。
  • 向套接字写入一些数据时,localhost 连接也开始发出警告,但并非总是如此。
  • 当它发出警告时,connection_lost 不会被调用。为什么?

修改代码:

import asyncio
import logging
import warnings

class ClientConnection(asyncio.Protocol):
    def connection_made(self, transport):
        logging.debug('connection_made')
        self.transport = transport
        transport.write(b'1234\r')
        transport.write_eof()  # Send EOF to close connection
        #transport.close()  # Cannot close here either, gives 'OSError: [WinError 10038]'

    def connection_lost(self, exception):
        logging.debug('connection_lost')
        super().connection_lost(exception)

    def data_received(self, data):
        logging.debug('received {} bytes'.format(len(data)))

    def eof_received(self):
        logging.debug('EOF received')
        self.transport.close()

def main():
    logging.basicConfig(level=logging.DEBUG)
    eventLoop = asyncio.get_event_loop()
    eventLoop.set_debug(True)
    warnings.simplefilter('always', ResourceWarning)  # enables ResourceWarning
    #co = eventLoop.create_connection(ClientConnection, '127.0.0.1', 7001)     # Works without warning
    co = eventLoop.create_connection(ClientConnection, '192.168.10.66', 7001)  # Gives warning
    try:
        eventLoop.run_until_complete(co)
        logging.debug('done')
    finally:
        eventLoop.close()

if __name__ == "__main__":
    main()

成功时输出:

...
DEBUG:root:EOF received
DEBUG:root:connection_lost
DEBUG:root:done
DEBUG:asyncio:Close <_WindowsSelectorEventLoop running=False closed=False debug=True>

失败时的输出(注意缺少connection_lost):

...
DEBUG:root:EOF received
DEBUG:root:done
DEBUG:asyncio:Close <_WindowsSelectorEventLoop running=False closed=False debug=True>
sys:1: ResourceWarning: unclosed <socket.socket fd=240, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto
=6, laddr=('127.0.0.1', 63858), raddr=('127.0.0.1', 7001)>
C:\Program Files (x86)\Python36-32\lib\asyncio\selector_events.py:631: ResourceWarning: unclosed transport <_SelectorSoc
ketTransport closing fd=240>
  source=self)

【问题讨论】:

  • 无法在 linux 上重现 WinError :-) transport.close() 工作正常。尝试在.close() 之前添加time.sleep(0.05)
  • @Udi Timeout 似乎没有帮助,Windows 似乎对某些事情很挑剔。我可能会问另一个问题。 但是,单独使用延迟似乎可以防止最初的问题。我需要做一些测试,但可能是我的程序做得太快了。感谢您迄今为止的帮助。
  • 显然在windows下write_eof调用close()。另外:尝试永远运行循环并在connection_loststop()它。

标签: python sockets python-asyncio


【解决方案1】:

要么等待服务器关闭你的连接,要么使用transport.close() 关闭传输。这也会触发connection_lost(不要从connection_lost调用transport.close()!):

class ClientConnection(asyncio.Protocol):
    def connection_made(self, transport):
        logging.debug("connection made, calling write eof")
        transport.write_eof()
        logging.debug("after calling write eof, calling close")
        transport.close()
        logging.debug("after calling close")

    def connection_lost(self, exception):
        logging.debug("connection lost")
        super().connection_lost(exception)

如果您尝试更多,您可能还会为您的本地计算机获得一些ResourceWarnings。例如,尝试在write_eof() 之前添加transport.write(b'hello world!') 或让本地服务器响应更慢。

【讨论】:

  • 您对向本地主机套接字发送数据是正确的:它也会触发警告。但我也无法关闭来自connection_made 的传输,我开始收到 OSErrors。我已经修改了问题。
  • 发布这是一个新问题会更有意义
  • OSError 可能需要转到另一个问题(我会进行编辑以排除该问题),但这个问题仍然悬而未决:有时connection_lost 未被调用,这会发出警告。
  • 考虑将主题改为“connection_lost not called”
【解决方案2】:

尝试多运行一下循环。下面是一个使用loop.run_forever()loop.stop() 的示例,假设在本地和远程机器上使用echo servers:

import asyncio
import logging

import warnings


class ClientConnection(asyncio.Protocol):
    def connection_made(self, transport):
        logging.debug("connection made, sending and calling write eof")
        transport.write(b'hello')
        transport.write_eof()
        logging.debug("after calling write eof")

    def data_received(self, data):
        logging.debug("Got: {}".format(data))
        super().data_received(data)

    def connection_lost(self, exception):
        logging.debug("connection lost")
        super().connection_lost(exception)
        loop.stop()


def test_echo(ip, port):
    logging.debug("Creating connection: {}:{}".format(ip, port))
    co = loop.create_connection(ClientConnection, ip, port)
    logging.debug("Starting loop...")
    print(loop.run_until_complete(co))
    logging.debug("...create_connection done, running loop forever...")
    loop.run_forever()
    logging.debug("Loop stopped")

    logging.debug('----------------')


if __name__ == "__main__":
    logging.basicConfig(level=logging.DEBUG)
    warnings.simplefilter('always')  # enables ResourceWarning

    loop = asyncio.get_event_loop()
    loop.set_debug(True)

    test_echo('127.0.0.1', 7001)
    test_echo('54.175.103.105', 30000)

    logging.debug("done")
    loop.close()

【讨论】:

    猜你喜欢
    • 2012-10-03
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 2020-05-01
    • 1970-01-01
    • 2016-04-24
    • 2017-03-10
    • 2012-11-21
    相关资源
    最近更新 更多