2

I am not able to detect socket client closing in a particular network. I am running a socket server and once a client connects I am saving the client socket and periodically sending a request to the client . I am using select.poll then to check if there is any data to be read from the socket, and if there is , will read from the socket. All this is fine as of now.

Question is , if the remote socket client is terminated, will select.poll signal a read event in the client socket. If this happens then I can check the data length returned in socket.recv to detect the client has disconnected - as is described here

Adding a code snippet for select

    def _wait_for_socket_poller(self, read, write, message=None):
    """
    Instead of blockign wait, this polls and check if the read or write socket is ready. If so it proceeds with
    reading or writing to the socket. The advantage is that while the poll blocks, it yeilds back to the other
    waiting greenlets; poll blocks because we have not given a timeout

    :param read: The read function
    :param write: The write function
    :param message: The CrowdBox API call
    :return: The result : In case of read - In JSON format; But catch is that the caller cannot wait on the
    result being available,as else the thread will block
    """
    if not self.client_socket:
        logging.error("CB ID =%d - Connection closed", self.id)
        return

    poller = select.poll()

    # Commonly used flag setes
    READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
    WRITE_ONLY = select.POLLOUT
    READ_WRITE = READ_ONLY | select.POLLOUT

    if read and write:
        poller.register(self.client_socket, READ_WRITE)
    elif write:
        poller.register(self.client_socket, WRITE_ONLY)
    elif read:
        poller.register(self.client_socket, READ_ONLY)

    # Map file descriptors to socket objects
    fd_to_socket = {self.client_socket.fileno(): self.client_socket, }
    result = ''
    retry = True
    while retry:
        # Poll will Block!!
        events = poller.poll(
            1)  # using poll instead of select as the latter runs out of file descriptors on load
        # Note here, Poll needs to timeout or will block ,as there is no gevent patched poll, the moment it blocks
        # neither greenlets or Twisted Deffered can help -Everything freezes,as all of this is in main thread
        if not events:
            retry = True
            gevent.sleep(0)  # This is needed to yeild in case no input comes from CB
        else:
            retry = False

    clientsock = None
    fd = None
    flag = None
    for fd, flag in events:
        # Retrieve the actual socket from its file descriptor to map return of poll to socket
        clientsock = fd_to_socket[fd]

    if clientsock is None:
        logging.error("Problem Houston")
        raise ValueError("Client Sokcet has Become Invalid")

    if flag & select.POLLHUP:
        logging.error("Client Socket Closed")
        self.client_socket.close()
        self.client_socket = None
        return None

    if flag & (select.POLLIN | select.POLLPRI):
        if read:
            result = read()
    if flag & select.POLLOUT:
        if write:
            result = write(message)
    # poller.uregister(self.client_socket)
    return result
Community
  • 1
  • 1
Alex Punnen
  • 5,287
  • 3
  • 59
  • 71

2 Answers2

0

In general, yes, a socket will be marked as "readable" when a TCP connection is closed. But this assumes that there was a normal closing, meaning a TCP FIN or RST packet.

Sometimes TCP connections don't end that way. In particular, if TCP Keep-Alive is not enabled (and by default it is not), a network outage between server and client could effectively terminate the connection without either side knowing until they try to send data.

So if you want to make sure you are promptly notified when a TCP connection is broken, you need to send keep-alive messages at either the TCP layer or the application layer.

Keep-alive messages have the additional benefit that they can prevent unused connections from being automatically dropped by various network appliances due to long periods of inactivity.

For more on keep-alive, see here: http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • I am planning to handle by sending a request to the client and then checking; first if socket.send is succeeding; I am finding it that writing the second time gives exception socket.error errno.EPIPE; Also select.poll is triggering socket read (POLLIN) and when we read using sokcet.recv we get data length as 0. And yes select.poll on the closed socket is marking it as readable, but as you said, even if that does not work in all networks, at least write should fail. Will update with live test result in the network where this problem happened – Alex Punnen May 02 '16 at 10:24
  • the above did not work; see details below.finally handled a keep alive protocol in the application layer. – Alex Punnen May 10 '16 at 02:39
0

Thought of adding an anwer here so that I can post some tcp dump trace. We tested this in a live network. The Socket client process in the remote machine terminated and python socket.send ( on a non blocking socket) client_socket.setblocking(0), did not return any error, for subsequent request send to the client from the server There was no event generated to indicate (EPOLLIN) something to read either.

So to detect the client connection loss, we ping the client periodically and if there is no expected response after three retrials , disconnect the client. Basically handled this in the application layer. Clients also changed to reply with some data for our 'are you alive' requests instead of just ignoring it.

    sent = 0
    try:
        sent = self.client_socket.send(out)
    except socket.error as e:
        if e.args[0] == errno.EPIPE:
        logging.error("Socket connection is closed or broken")
    if sent == 0 and self.client_socket is not None:
        logging.error("socket connection is already closed by client, cannot write request")
        self.close_socket_connection()
    else
      # send succcessfully

Below is the tcpdump wireshark trace where you can see the re-transmit happening. IP details masked for security tcpdump wireshark trace

Alex Punnen
  • 5,287
  • 3
  • 59
  • 71