How to tell if a connection is dead in python

Viewed 178912

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?

6 Answers

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.

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

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

Trying to improve on @kay response. I made a more pythonic version

(Note that it was not yet tested in a "real-life" environment, and only on Linux)

This detects if the remote side closed the connection, without actually consuming the data:

import socket
import errno


def remote_connection_closed(sock: socket.socket) -> bool:
    """
    Returns True if the remote side did close the connection

    """
    try:
        buf = sock.recv(1, socket.MSG_PEEK | socket.MSG_DONTWAIT)
        if buf == b'':
            return True
    except BlockingIOError as exc:
        if exc.errno != errno.EAGAIN:
            # Raise on unknown exception
            raise
    return False

Here is a simple example from an asyncio echo server:

import asyncio


async def handle_echo(reader, writer):
    addr = writer.get_extra_info('peername')
    sock = writer.get_extra_info('socket')
    print(f'New client: {addr!r}')

    # Initial of client command
    data = await reader.read(100)
    message = data.decode()

    print(f"Received {message!r} from {addr!r}")

    # Simulate a long async process
    for _ in range(10):
        if remote_connection_closed(sock):
            print('Remote side closed early')
            return
        await asyncio.sleep(1)

    # Write the initial message back
    print(f"Send: {message!r}")
    writer.write(data)
    await writer.drain()
    writer.close()


async def main():
    server = await asyncio.start_server(
        handle_echo, '127.0.0.1', 8888)

    addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets)
    print(f'Serving on {addrs}')

    async with server:
        await server.serve_forever()


if __name__ == '__main__':
    asyncio.run(main())
Related