How to tell if a connection is dead in python

前端 未结 5 606
忘掉有多难
忘掉有多难 2020-11-29 23:41

I want my python application to be able to tell when the socket on the other side has been dropped. Is there a method for this?

相关标签:
5条回答
  • 2020-11-29 23:59

    If I'm not mistaken this is usually handled via a timeout.

    0 讨论(0)
  • 2020-11-30 00:00

    It depends on what you mean by "dropped". For TCP sockets, if the other end closes the connection either through close() or the process terminating, you'll find out by reading an end of file, or getting a read error, usually the errno being set to whatever 'connection reset by peer' is by your operating system. For python, you'll read a zero length string, or a socket.error will be thrown when you try to read or write from the socket.

    0 讨论(0)
  • 2020-11-30 00:09

    Short answer:

    use a non-blocking recv(), or a blocking recv() / select() with a very short timeout.

    Long answer:

    The way to handle socket connections is to read or write as you need to, and be prepared to handle connection errors.

    TCP distinguishes between 3 forms of "dropping" a connection: timeout, reset, close.

    Of these, the timeout can not really be detected, TCP might only tell you the time has not expired yet. But even if it told you that, the time might still expire right after.

    Also remember that using shutdown() either you or your peer (the other end of the connection) may close only the incoming byte stream, and keep the outgoing byte stream running, or close the outgoing stream and keep the incoming one running.

    So strictly speaking, you want to check if the read stream is closed, or if the write stream is closed, or if both are closed.

    Even if the connection was "dropped", you should still be able to read any data that is still in the network buffer. Only after the buffer is empty will you receive a disconnect from recv().

    Checking if the connection was dropped is like asking "what will I receive after reading all data that is currently buffered ?" To find that out, you just have to read all data that is currently bufferred.

    I can see how "reading all buffered data", to get to the end of it, might be a problem for some people, that still think of recv() as a blocking function. With a blocking recv(), "checking" for a read when the buffer is already empty will block, which defeats the purpose of "checking".

    In my opinion any function that is documented to potentially block the entire process indefinitely is a design flaw, but I guess it is still there for historical reasons, from when using a socket just like a regular file descriptor was a cool idea.

    What you can do is:

    • set the socket to non-blocking mode, but than you get a system-depended error to indicate the receive buffer is empty, or the send buffer is full
    • stick to blocking mode but set a very short socket timeout. This will allow you to "ping" or "check" the socket with recv(), pretty much what you want to do
    • use select() call or asyncore module with a very short timeout. Error reporting is still system-specific.

    For the write part of the problem, keeping the read buffers empty pretty much covers it. You will discover a connection "dropped" after a non-blocking read attempt, and you may choose to stop sending anything after a read returns a closed channel.

    I guess the only way to be sure your sent data has reached the other end (and is not still in the send buffer) is either:

    • receive a proper response on the same socket for the exact message that you sent. Basically you are using the higher level protocol to provide confirmation.
    • perform a successful shutdow() and close() on the socket

    The python socket howto says send() will return 0 bytes written if channel is closed. You may use a non-blocking or a timeout socket.send() and if it returns 0 you can no longer send data on that socket. But if it returns non-zero, you have already sent something, good luck with that :)

    Also here I have not considered OOB (out-of-band) socket data here as a means to approach your problem, but I think OOB was not what you meant.

    0 讨论(0)
  • 2020-11-30 00:17

    I translated the code sample in this blog post into Python: How to detect when the client closes the connection?, and it works well for me:

    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')
    
    0 讨论(0)
  • 2020-11-30 00:21

    From the link Jweede posted:

    exception 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”.
    

    Here are the demo server and client programs for the socket module from the 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()
    

    And the client:

    # 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)
    

    On the docs example page I pulled these from, there are more complex examples that employ this idea, but here is the simple answer:

    Assuming you're writing the client program, just put all your code that uses the socket when it is at risk of being dropped, inside a try block...

    try:
        s.connect((HOST, PORT))
        s.send("Hello, World!")
        ...
    except socket.timeout:
        # whatever you need to do when the connection is dropped
    
    0 讨论(0)
提交回复
热议问题