【问题标题】:How to tell if a connection is dead in python如何判断python中的连接是否已死
【发布时间】:2010-10-14 15:16:12
【问题描述】:

我希望我的 python 应用程序能够判断另一侧的套接字何时被删除。有这个方法吗?

【问题讨论】:

  • 您在阅读(并获得 EOF)吗?或者你在写(并得到 I/O 错误)?还是只是等待使用 select?

标签: python sockets


【解决方案1】:

如果我没记错的话,这通常是通过timeout 处理的。

【讨论】:

    【解决方案2】:

    来自 Jweede 发布的链接:

    socket.timeout 异常:

    This exception is raised when a timeout occurs on a socket
    which has had timeouts enabled via a prior call to settimeout().
    The accompanying value is a string whose value is currently
    always “timed out”.
    

    这里是来自python docs的socket模块的演示服务器和客户端程序

    # Echo server program
    import socket
    
    HOST = ''                 # Symbolic name meaning all available interfaces
    PORT = 50007              # Arbitrary non-privileged port
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(1)
    conn, addr = s.accept()
    print 'Connected by', addr
    while 1:
        data = conn.recv(1024)
        if not data: break
        conn.send(data)
    conn.close()
    

    还有客户:

    # Echo client program
    import socket
    
    HOST = 'daring.cwi.nl'    # The remote host
    PORT = 50007              # The same port as used by the server
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))
    s.send('Hello, world')
    data = s.recv(1024)
    s.close()
    print 'Received', repr(data)
    

    在我从中提取这些的文档示例页面上,有更复杂的示例采用了这个想法,但这里是简单的答案:

    假设您正在编写客户端程序,只需将所有使用套接字的代码(当它有被丢弃的风险)放在一个 try 块中......

    try:
        s.connect((HOST, PORT))
        s.send("Hello, World!")
        ...
    except socket.timeout:
        # whatever you need to do when the connection is dropped
    

    【讨论】:

    • 谢谢。这正是我回答相关但不同的问题所需要的......
    • send() 不会触发超时。只有connect()recv() 这样做。
    【解决方案3】:

    这取决于您所说的“丢弃”是什么意思。对于 TCP 套接字,如果另一端通过以下方式关闭连接 close() 或进程终止,您将通过读取文件结尾或收到读取错误来发现,通常将 errno 设置为操作系统设置的任何“对等连接重置”。对于 python,你将读取一个长度为零的字符串,或者当你尝试从套接字读取或写入时会抛出一个 socket.error。

    【讨论】:

      【解决方案4】:

      简答:

      使用非阻塞recv(),或阻塞recv() / select() 短暂的超时。

      长答案:

      处理套接字连接的方法是根据需要读取或写入,并准备处理连接错误。

      TCP 区分“断开”连接的 3 种形式:超时、重置、关闭。

      其中,超时并不能真正检测到,TCP 可能只会告诉你时间还没有到期。但即使它告诉你,时间也可能马上就到期了。

      还请记住,您或您的对等方(连接的另一端)使用 shutdown() 可能只关闭传入字节流,并保持传出字节流运行,或者关闭传出流并保持传入字节流运行.

      所以严格来说,你要检查读取流是否关闭,或者写入流是否关闭,或者两者都关闭。

      即使连接被“断开”,您仍然应该能够读取仍在网络缓冲区中的任何数据。只有在缓冲区为空后,您才会收到与 recv() 的断开连接。

      检查连接是否断开就像问“读取当前缓冲的所有数据后我会收到什么?”要找出这一点,您只需读取当前缓冲的所有数据。

      我可以看到“读取所有缓冲的数据”对于一些人来说可能是个问题,他们仍然认为 recv() 是一个阻塞函数。使用阻塞的 recv(),当缓冲区已经为空时“检查”读取将阻塞,这违背了“检查”的目的。

      在我看来,任何被记录为可能无限期阻塞整个过程的函数都是一个设计缺陷,但我想它仍然存在由于历史原因,从当使用套接字时就像一个常规文件描述符是一个很酷的想法。

      你可以做的是:

      • 将套接字设置为非阻塞模式,但您会收到一个与系统相关的错误,指示接收缓冲区为空或发送缓冲区已满
      • 坚持阻塞模式,但设置一个非常短的套接字超时。这将允许您使用 recv()“ping”或“检查”套接字,这几乎是您想要做的事情
      • 使用 select() 调用或异步模块,超时时间很短。错误报告仍然是系统特定的。

      对于问题的写入部分,保持读取缓冲区为空几乎可以解决问题。在非阻塞读取尝试后,您会发现连接“断开”,并且您可以选择在读取返回关闭的通道后停止发送任何内容。

      我想确保您发送的数据已到达另一端(并且不在发送缓冲区中)的唯一方法是:

      • 在同一个套接字上接收您发送的确切消息的正确响应。基本上,您正在使用更高级别的协议来提供确认。
      • 在套接字上执行成功的shutdow()和close()

      python socket howto 说如果通道关闭,send() 将返回 0 个字节写入。您可以使用非阻塞或超时 socket.send() 如果它返回 0,则您不能再在该套接字上发送数据。但是如果它返回非零,你已经发送了一些东西,祝你好运:)

      另外,在这里我没有考虑将 OOB(带外)套接字数据作为解决您的问题的一种方法,但我认为 OOB 不是您的意思。

      【讨论】:

      • 请注意,这样做会删除一些可供阅读的数据。如果在另一个线程从套接字读取数据时执行此操作,则正在读取的数据中会丢失一小块。
      • 超时不是 TCP“断开连接”的一种形式。它只是说在超时期限内没有数据到达。连接仍然存在并且可能仍然可用。
      【解决方案5】:

      我将这篇博文中的代码示例翻译成 Python:How to detect when the client closes the connection?,对我来说效果很好:

      from ctypes import (
          CDLL, c_int, POINTER, Structure, c_void_p, c_size_t,
          c_short, c_ssize_t, c_char, ARRAY
      )
      
      
      __all__ = 'is_remote_alive',
      
      
      class pollfd(Structure):
          _fields_ = (
              ('fd', c_int),
              ('events', c_short),
              ('revents', c_short),
          )
      
      
      MSG_DONTWAIT = 0x40
      MSG_PEEK = 0x02
      
      EPOLLIN = 0x001
      EPOLLPRI = 0x002
      EPOLLRDNORM = 0x040
      
      libc = CDLL('libc.so.6')
      
      recv = libc.recv
      recv.restype = c_ssize_t
      recv.argtypes = c_int, c_void_p, c_size_t, c_int
      
      poll = libc.poll
      poll.restype = c_int
      poll.argtypes = POINTER(pollfd), c_int, c_int
      
      
      class IsRemoteAlive:  # not needed, only for debugging
          def __init__(self, alive, msg):
              self.alive = alive
              self.msg = msg
      
          def __str__(self):
              return self.msg
      
          def __repr__(self):
              return 'IsRemoteAlive(%r,%r)' % (self.alive, self.msg)
      
          def __bool__(self):
              return self.alive
      
      
      def is_remote_alive(fd):
          fileno = getattr(fd, 'fileno', None)
          if fileno is not None:
              if hasattr(fileno, '__call__'):
                  fd = fileno()
              else:
                  fd = fileno
      
          p = pollfd(fd=fd, events=EPOLLIN|EPOLLPRI|EPOLLRDNORM, revents=0)
          result = poll(p, 1, 0)
          if not result:
              return IsRemoteAlive(True, 'empty')
      
          buf = ARRAY(c_char, 1)()
          result = recv(fd, buf, len(buf), MSG_DONTWAIT|MSG_PEEK)
          if result > 0:
              return IsRemoteAlive(True, 'readable')
          elif result == 0:
              return IsRemoteAlive(False, 'closed')
          else:
              return IsRemoteAlive(False, 'errored')
      

      【讨论】:

      • 您的解决方案看起来非常好,但是:init 中的文件“C:\Python37\lib\ctypes_init_.py”,第 356 行._handle = _dlopen(self._name, mode) TypeError: LoadLibrary() argument 1 must be str, not None ??
      • 您有解决方法吗?它正在轰炸:libc = CDLL(None)
      • 我使用的是 Windows 10、64 位
      • @JDOaktown,在 Linux 中你可以CDLL('libc.so.6')。我不知道它在 Windows 的 Linux 子系统中是否相同。抱歉,我知道我没有安装 Windows 来测试它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-31
      • 2011-10-23
      • 2012-05-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-18
      相关资源
      最近更新 更多