2

Detect remote disconnect (half-close) when using asynchronous sockets in C/C++

I have implemented a TCP/IP client using C sockets running in asynchronous mode (fcntl(sock, F_SETFL, O_ASYNC);) in order to minimise blocking. The client will only send data to the server and never receive any data. Thus the loop looks something like this:

while (true) {
    ...
    fd_set writefds;
    FD_ZERO(&writefds);
    FD_SET(socketFd, &writefds);
    select(socketFd + 1, NULL, &writefds, NULL, &selectTimeout);
    if (FD_ISSET(socketFd, &writefds)) {
       ssize_t cBytes = send(socketFd, someBuffer, someSize, 0);
       ...
    }
    ...
 }

How do I detect if the server disconnects? As suggested in this question, an initially plausible solution is to call recv(socketFd, &buf, len, MSG_PEEK); and check if that call returns 0. However, this clearly does not work because first of all, the server does not actually send any data in my case. Second, in the generalised case the server might not send data all the time and recv() will thus return 0 anyway as it is non-blocking.

When using tcpdump to monitor the connection between the server and the client, the server sends a FIN packet when it closes the connection but the client does not acknowledge this. I have been unable to make it recognise the half-close using neither recv(), send(), nor select(). In fact, the first indication that something is wrong is when the server sends a RST when the client tries to send data to the now closed connection:

IP6 ::1.62179 > ::1.2526: Flags [S], seq ..., length 0
IP6 ::1.2526 > ::1.62179: Flags [S.], seq ..., length 0
IP6 ::1.62179 > ::1.2526: Flags [.], ack 1, ..., length 0
IP6 ::1.2526 > ::1.62179: Flags [.], ack 1, ..., length 0
IP6 ::1.2526 > ::1.62179: Flags [F.], seq 1, ..., length 0
IP6 ::1.62179 > ::1.2526: Flags [.], ack 2, ..., length 0
IP6 ::1.2526 > ::1.62179: Flags [.], ack 1, ..., length 0
IP6 ::1.62179 > ::1.2526: Flags [P.], seq 1:7, ack 2, ..., length 6
IP6 ::1.2526 > ::1.62179: Flags [R], seq ..., length 0

How do I determine that the server has sent the FIN packet so that I can cleanly shutdown everything before trying to send the next data packet?

Community
  • 1
  • 1
Jonas Nilsson
  • 85
  • 1
  • 10

2 Answers2

2

The solution to this problem is to use poll() as defined in sys/poll.h. poll() will set the POLLHUP bit in a pollfd struct if a FIN has been received on the socket:

#include <sys/poll.h>

...

pollfd ufds[1];
ufds[0].fd = socketFd;
ufds[0].events = POLLIN;
int pollRes = poll(ufds, 1, 0);
if (pollRes == 1) {
    if (ufds[0].revents & POLLHUP) {
        std::cout << "Got FIN, closing socket." << std::endl;
        shutdown(socketFd, SHUT_RDWR);
        close(socketFd);
        ...
    }
}
Jonas Nilsson
  • 85
  • 1
  • 10
1

the server might not send data all the time and recv() will thus return 0 anyway as it is non-blocking.

No. recv will not return 0 if you call it on a active connection. It returns the number of bytes read or -1 if an error occurred (check errno). It returns 0 only if peer had performed orderly shutdown check the man page

How do I determine that the server has sent the FIN packet so that I can cleanly shutdown everything before trying to send the next data packet?

Your code should do the recv, determine the return value for next action. A return of 0 indicates the peer has closed the connection.

Prabhu
  • 3,443
  • 15
  • 26
  • Looking through my code again I found the reason this solution did not work. I was hiding all calls to `recv()` behind a call to `select()` and as there was never any data to receive `recv()` was never actually called. The solution in this case is thus to call `recv()` periodically regardless of the result of `select()` and ignore it when `recv()` returns an error (-1) but close the socket when no error is received (0). – Jonas Nilsson Dec 22 '16 at 14:00
  • For non-blocking sockets you need to depend on mechanisms like `select`, `poll` , `epoll`. Based on the event take further action. You need not periodically call `recv`. Let the polling mechanism inform the socket event to the application – Prabhu Dec 22 '16 at 14:59