如何判断python中的连接是否已经死亡

时间:2009-03-20 19:31:50

标签: python sockets

我希望我的python应用程序能够分辨出另一端的套接字何时被删除。有没有办法呢?

5 个答案:

答案 0 :(得分:43)

简短回答:

  

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

答案很长:

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

TCP区分3种“丢弃”连接形式:超时,重置,关闭。

其中,无法真正检测到超时,TCP可能只会告诉您时间尚未到期。但即使它告诉你,时间可能仍然会过期。

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

严格来说,您要检查读取流是否已关闭,或者写入流是否已关闭,或两者是否已关闭。

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

检查连接是否被丢弃就像问“在读取当前缓冲的所有数据后我会收到什么?”要找到它,您只需要读取当前缓冲的所有数据。

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

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

你能做的是:

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

对于问题的写入部分,保持读取缓冲区空白几乎涵盖了它。在非阻塞读取尝试后,您将发现“丢弃”连接,并且您可以选择在读取返回已关闭的通道后停止发送任何内容。

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

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

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

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

答案 1 :(得分:34)

这取决于你所说的“掉线”。对于TCP套接字,如果另一端通过关闭连接 close()或进程终止,您将通过读取文件结尾或获取读取错误来查找,通常将错误设置为“通过对等方重置连接”的任何操作系统。对于python,您将读取零长度字符串,或者当您尝试从套接字读取或写入时将抛出socket.error。

答案 2 :(得分:14)

来自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

的套接字模块的演示服务器和客户端程序
# 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

答案 3 :(得分:4)

如果我没弄错,通常会通过timeout处理。

答案 4 :(得分:4)

我将此博客文章中的代码示例翻译成了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(None)

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 'IsRemoteClosed(%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')