1

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;
}
xaxxon
  • 19,189
  • 5
  • 50
  • 80
user2399415
  • 207
  • 2
  • 3
  • 8
  • Yeah, I don't get what the problem is. What exactly are you expecting? – xaxxon Jul 24 '13 at 05:02
  • I'm expecting to be returned -1. Because when socket is closed, it should return -1 on error. Non-blocking sockets can receive 0 bytes which means there is no data available yet, I don't quite understood what should I check or not. – user2399415 Jul 24 '13 at 11:39
  • no, you get an "error" when there's no data, you get 0 when there is a disconnect. Don't think "error" means error. Just accept it and go forward :) You're getting what you should get - it's weird, but it's correct. – xaxxon Jul 24 '13 at 20:03

2 Answers2

8

recv() returns zero when the peer disconnects. You don't have a problem. This is expected, correct behaviour.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • I would add that recv() return zero when the peer disconnects *gracefully*. In the event of a network error (e.g peer crashes or loses network connectivity), recv() may return -1. – selbie Jul 24 '13 at 04:56
  • I chose my words carefully. Those aren't disconnects by the peer, they are network errors, usually resulting in `ECONNRESET` being issued by the local host as a result of *it* disconnecting. – user207421 Jul 24 '13 at 05:38
  • Nope, it returns 0 sometimes without closing the socket, which means there is no data available yet. I said, I'm using non-blocking sockets. – user2399415 Jul 24 '13 at 11:39
  • 1
    @selbie: One should note that this usually _doesn't_ happen, or it only happens with considerable delay, _if at all_. Loss of connection due to the broad variety of "network errors" is not detected as long as nothing is sent, and it is not detected on the receiving end at all. The only way to detect a broken link is to send and get an error back. `recv` on the other hand, will not do anything other than block or return `EWOULDBLOCK` if there's nothing. Keepalive probes would be an exception to this, but they're sent so rarely (2 hours) that they really don't count. – Damon Jul 24 '13 at 14:24
  • The point I was making was that I wanted the OP to check for both 0 and -1 as a return call from recv(). If he's using non-blocking sockets, he would need to check for errno==EAGAIN || errno==EWOULDBLOCK as well. – selbie Jul 24 '13 at 15:34
  • 1
    @selbie: That's right, but to me your first comment sounded like `recv` could detect network errors (and report them by returning -1), which it doesn't. A non-blocking socket _always_ generates `EWOULDBLOCK` (or `EAGAIN` if you will) when there's nothing available (and a blocking one just... well, blocks). This includes network errors of course, but it also includes the other end simply not sending anyhting, or sent packets still being "on the wire" or somewhere within the TCP stack. – Damon Jul 24 '13 at 16:59
  • @user2399415 "it returns 0 sometimes without closing the socket, which means there is no data available yet". No it doesn't. It means the peer closed the connection. No data yet is signalled by `recv()` returning -1 with `errno` equal to `EAGAIN` or `EWOULDBLOCK.` – user207421 Oct 22 '13 at 03:38
  • @Damon EAGAIN/EWOULDBLOCK does not include network errors. If there has been a detected network error it will yield ECONNRESET. However `recv()` by itself doesn't do anything to *cause* network errors, so unless there was a pending write or a deliberate reset from the peer they won't occur to be detected. – user207421 May 23 '16 at 08:51
5

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.

lorond
  • 3,856
  • 2
  • 37
  • 52
ja_mesa
  • 1,971
  • 1
  • 11
  • 7
  • So, if recv() <= 0, ioctl() <= 0 will be disconnect? – user2399415 Jul 24 '13 at 11:41
  • @user2399415 See my edited answer for an explanation. – ja_mesa Jul 24 '13 at 13:47
  • Thanks for reply, but the fun part is that, log shows me this: received 1 bytes, received 5 bytes, received 0 bytes, received 0 bytes, ..., received 1 bytes, received 1 bytes, done receiving. Why? EDIT these numbers are return values of recv() call. – user2399415 Jul 24 '13 at 14:08
  • Instead of reading bytesAvailable try reading the whole buffer, sizeof(buffer) or the size you have allocated with malloc(). May be select execute too fast and ioctl returns 1, 2 ,5 when there are really some more bytes coming in. They put your question in [hold]. You must ask a specific question and try avoiding using this as a chat room. – ja_mesa Jul 24 '13 at 15:27
  • Thanks, seems to working fine for now. – user2399415 Jul 25 '13 at 19:41