user2399415 user2399415 - 3 months ago 41
C Question

non-blocking recv returns 0 when disconnected

I'm trying to 'catch' when disconnect happens.
But I actually don't get what's wrong. recv() returns 0, errno is set to 0 and ioctl returns 0. I was searching like 6 hours on the web but without success. Can anyone tell me what's wrong?

Regards.

bool Network::setBlocking(bool blocking)
{
// sets blocking or non-blocking mode.

int flags = blocking ? 1 : 0;
return ioctl(this->sockfd, FIONBIO, &flags) ? false : true;
}

NetworkStatus Network::status()
{
// returns socket status.

struct timeval tv;
fd_set fd;
int result = 0;

tv.tv_sec = 3;
tv.tv_usec = 0;

FD_ZERO(&fd);
FD_SET(this->sockfd, &fd);

result = select(this->sockfd + 1, &fd, 0, 0, &tv);

if(result == -1)
{
return NETWORK_ERROR;
}
else if(result)
{
return NETWORK_READYREAD;
}
else
{
return NETWORK_TIMEOUT;
}
}

int Network::bytesAvailable()
{
// returns number of bytes available.

int bytes = 0;

if(ioctl(this->sockfd, FIONREAD, &bytes) < 0 || errno)
{
return -1;
}

return bytes;
}

int Network::read(char *buffer, int size)
{
// reads data from socket.

return recv(this->sockfd, buffer, size, 0);
}
...
int main(int argc, char* argv[])
{
Network net;
...
while(true)
{
switch(net.status())
{
...
case NETWORK_READYREAD:
{
int bytesAvailable = net.bytesAvailable();
char temp[bytesAvailable];
int len = net.read(temp, bytesAvailable);
printf("len: %d\nerrno: %d\nbytesAvailable: %d\n", len, errno, bytesAvailable); // len: 0, errno: 0, bytesAvailable: 0
break;
}
}
} // status
return 0;
}

Answer

When the other side close the socket or do a shutdown(Send), at your side the select will return that the socket is readable, so the select command will return 1 indicating there is data to be read (in fact there no data, it is just a signal indicating the socket is closed), but the ioctl return 0 as bytes available and the recv() also return 0 and that's exactly how TCP works. So in your program you should take care of bytesAvaliable returns 0 and/or read() returns 0. In these cases the socket has been closed and you should close your side as well and waits (accept) or establish (connect) a new connection.

EDIT: Add commnents:

Well, not exactly. When recv() = 0, the socket is closed or shutdown for send, when = -1 there is an error in the socket. If the socket is non-blocking (has O_NONBLOCK set) you should check for an error (errno on unix, WSAGetLastError() on Windows) and EWOULDBLOCK or EAGAIN indicates there is no data available and the socket is still valid. Also, in some very rare cases, recv() could return -1 and the error EINTR which means that during the recv() operation an interrupt (signal) has been received, in this case you should ignore the error (unless you are waiting for such signal) and reissue the recv().

ret=recv(socket, buff, length, 0);
if (ret > 0) 
{
    // data has been received
}
else if (ret == 0) 
{
    // socket has been closed or shutdown for send
}
else {
   // there is an error, let's see what it is
   int error = errno; // or WSAGetLastError()
   switch (error)
   {
         case EAGAIN:
         case EWOULDBLOCK: 
              // Socket is O_NONBLOCK and there is no data available
              break;
         case EINTR: 
              // an interrupt (signal) has been catched
              // should be ingore in most cases
              break;
         default:
              // socket has an error, no valid anymore
              return;
   }
}

Saying that, one thing you must be clear about is that if recv() return 0 or -1 the socket is not valid anymore and should be closed, but again, depends on many factors, the protocol you use to keep a dialog between sockets, if it is in non-blocking, etc. For example, the other side could issue the shutdown(); in this case your recv() will return 0 but you could still do a send() through that socket without errors.