我正在使用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)
    

    第二次尝试

    我对代码进行了以下更改:
  • transport.close()删除connection_lost
  • 将一些数据写入连接
  • 添加了data_receivedeof_received回调
  • 添加了更多调试日志

  • 观察结果:
  • 我尝试将transport.close()添加到connection_made中,但始终会导致OSError: [WinError 10038]。注意:这可能是另一个问题,所以让我们暂时忽略它,并假设我不会这样做。
  • 将一些数据写入套接字时,本地主机连接也开始发出警告,但并不总是这样。
  • 发出警告时,不会调用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)
    

    最佳答案

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

    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)
    

    如果再尝试一些,您可能还会在本地计算机上得到一些ResourceWarning。例如,尝试在transport.write(b'hello world!')之前添加write_eof()或使本地服务器的响应速度变慢。

    关于python - 异步客户端: `connection_lost` not called,导致ResourceWarning,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41695498/

    10-12 18:41