Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine if peer has closed reading end of socket

Tags:

c

posix

sockets

I have a socket programming situation where the client shuts down the writing end of the socket to let the server know input is finished (via receiving EOF), but keeps the reading end open to read back a result (one line of text). It would be useful for the server to know that the client has successfully read the result and closed the socket (or at least shut down the reading end). Is there a good way to check/wait for such status?

like image 264
R.. GitHub STOP HELPING ICE Avatar asked Oct 17 '16 00:10

R.. GitHub STOP HELPING ICE


2 Answers

No. All you can know is whether your sends succeeded, and some of them will succeed even after the peer read shutdown, because of TCP buffering.

This is poor design. If the server needs to know that the client received the data, the client needs to acknowledge it, which means it can't shutdown its write end. The client should:

  1. send an in-band termination message, as data.
  2. read and acknowledge all further responses until end of stream occurs.
  3. close the socket.

The server should detect the in-band termination message and:

  1. stop reading requests from the socket
  2. send all outstanding responses and read the acknowledgements
  3. close the socket.

OR, if the objective is only to ensure that client and server end at the same time, each end should shutdown its socket for output and then read input until end of stream occurs, then close the socket. That way the final closes will occur more or less simultaneously on both ends.

like image 89
user207421 Avatar answered Oct 23 '22 07:10

user207421


getsockopt with TCP_INFO seems the most obvious choice, but it's not cross-platform.

Here's an example for Linux:

import socket
import time
import struct
import pprint


def tcp_info(s):
    rv = dict(zip("""
            state ca_state retransmits probes backoff options snd_rcv_wscale
            rto ato snd_mss rcv_mss unacked sacked lost retrans fackets
            last_data_sent last_ack_sent last_data_recv last_ack_recv
            pmtu rcv_ssthresh rtt rttvar snd_ssthresh snd_cwnd advmss reordering
            rcv_rtt rcv_space
            total_retrans
            pacing_rate max_pacing_rate bytes_acked bytes_received segs_out segs_in
            notsent_bytes min_rtt data_segs_in data_segs_out""".split(),
                  struct.unpack("BBBBBBBIIIIIIIIIIIIIIIIIIIIIIIILLLLIIIIII",
                                s.getsockopt(socket.IPPROTO_TCP, socket.TCP_INFO, 160))))
    wscale = rv.pop("snd_rcv_wscale")

    # bit field layout is up to compiler
    # FIXME test the order of nibbles
    rv["snd_wscale"] = wscale >> 4
    rv["rcv_wscale"] = wscale & 0xf
    return rv

for i in range(100):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost", 7878))
    s.recv(10)
    pprint.pprint(tcp_info(s))

I doubt a true cross-platform alternative exists.

Fundamentally there are quite a few states:

  • you wrote data to socket, but it was not sent yet
  • data was sent, but not received
  • data was sent and losts (relies on timer)
  • data was received, but not acknowledged yet
  • acknowledgement not received yet
  • acknowledgement lost (relies on timer)
  • data was received by remote host but not read out by application
  • data was read out by application, but socket still alive
  • data was read out, and app crashed
  • data was read out, and app closed the socket
  • data was read out, and app called shutdown(WR) (almost same as closed)
  • FIN was not sent by remote yet
  • FIN was sent by remote but not received yet
  • FIN was sent and got lost
  • FIN received by your end

Obviously your OS can distinguish quite a few of these states, but not all of them. I can't think of an API that would be this verbose...

Some systems allow you to query remaining send buffer space. Perhaps if you did, and socket was already shut down, you'd get a neat error?

Good news is just because socket is shut down, doesn't mean you can't interrogate it. I can get all of TCP_INFO after shutdown, with state=7 (closed). In some cases report state=8 (close wait).

http://lxr.free-electrons.com/source/net/ipv4/tcp.c#L1961 has all the gory details of Linux TCP state machine.

like image 27
Dima Tisnek Avatar answered Oct 23 '22 09:10

Dima Tisnek