swiftsocket / SwiftSocket

The easy way to use sockets on Apple platforms
BSD 3-Clause "New" or "Revised" License
1.68k stars 402 forks source link

Blocking ytcpsocket_pull #157

Open ermanitu opened 6 years ago

ermanitu commented 6 years ago

When reading from server side read will block. Located the problem in while loop at line 124 of ytcpsocket_pull. First read gets the data and in the next read (the loop continues) and in the next read there are no more data to reads.

I solved changing line 129 } while (readlen > 0);

to

} while (ytcpsocket_bytes_available(socketfd));

and relocating int ytcpsocket_bytes_available(int socketfd) before ytcpsocket_pull

fukemy commented 6 years ago

hello. u need to explain why the code have that magic, that can be solve your problem. We need to know both the problem and the solution, not only some lines of code. Many thanks. I believe that isn't the general problem, may be only in some special case, that why i ask some explain from you.

ermanitu commented 6 years ago

The problem is related with issue #127 and I guess it's a general problem with ytcp library.

In essence, I use two programs, client side and server side.

Client program open a connection with server, send a message (with send) and wait for a response (with read).

@objc func sendMessage (msg: String) -> String {
    let client: TCPClient = TCPClient(address: serverip, port: Int32(serverport))
    var res = client.connect(timeout: 1)
    if res.isFailure {
        print("Error connecting")
    }
    res = client.send(string: msg)
    if res.isFailure {
        print("Error sending")
    }
    //sleep(1) // wait for read, else the read will fail
    print(msg)
    let data = client.read(1024,timeout: 10)

    var message = "error"
    if data != nil {
        message = String(bytes: data!, encoding: String.Encoding.utf8)!
    }
    print(message)
    _ = client.close()

    DispatchQueue.main.async {
        if message == "error" {
            self.atvicon.image = UIImage(named: "atvicon_off.png")
            self.available = false
            if msg != "status" { // avoid error messages when Caravel-ATV is off
                self.alertInfo(title: "Error", message: "Not connected to Apple TV")                    
            }
        }
        else {
            self.atvicon.image = UIImage(named: "atvicon.png")
            self.available = true
        }
    }

    return message
}

Server program listen, accept connection and send response

@objc func runserver() {
    server = TCPServer(address: localIP, port: 8080)
    let res = server.listen()
    if res.isSuccess {
        while server != nil {
            if let client = server.accept() {
                ...
               _ = client.send(string: "ok")
            } else {
                print("accept error")
            }
        }
    } else {
        print("Error in server")
    }
}

After 4 hours of debugging i found the problem is in the read from client. This reads wait until timeout.

Entering more deep in the problem I found the read blocking is in the function ytcpsocket_pull

int ytcpsocket_pull(int socketfd, char *data, int len, int timeout_sec) {
  int readlen = 0;
  int datalen = 0;
  if (timeout_sec > 0) {
    fd_set fdset;
    struct timeval timeout;
    timeout.tv_usec = 0;
    timeout.tv_sec = timeout_sec;
    FD_ZERO(&fdset);
    FD_SET(socketfd, &fdset);
    int ret = select(socketfd + 1, &fdset, NULL, NULL, &timeout);
    if (ret <= 0) {
        return ret; // select-call failed or timeout occurred (before anything was sent)
  }
}
// use loop to make sure receive all data
  do {
    readlen = (int)read(socketfd, data + datalen, len - datalen);
    if (readlen > 0) {
        datalen += readlen;
    }
  } while (readlen > 0);

  return datalen;
}

This function has a while loop reading while readlen > 0, but after reading "ok" function tries to read more and get locked in the read because no more bytes.

I resolved asking if more bytes are available replacing readlen > 0.

The modified function resolving the problem is:

int ytcpsocket_pull(int socketfd, char *data, int len, int timeout_sec) {
  int readlen = 0;
  int datalen = 0;
  if (timeout_sec > 0) {
    fd_set fdset;
    struct timeval timeout;
    timeout.tv_usec = 0;
    timeout.tv_sec = timeout_sec;
    FD_ZERO(&fdset);
    FD_SET(socketfd, &fdset);
    int ret = select(socketfd + 1, &fdset, NULL, NULL, &timeout);
    if (ret <= 0) {
        return ret; // select-call failed or timeout occurred (before anything was sent)
    }
  }
  // use loop to make sure receive all data

do {
    readlen = (int)read(socketfd, data + datalen, len - datalen);
    //printf("leido: %d",readlen);
    if (readlen > 0) {
        datalen += readlen;
    }
} while (ytcpsocket_bytes_available(socketfd)); // esta línea se ha cambiado para evitar el bug

return datalen;
}

And of course I must to relocate ytcpsocket_bytes_available(socketfd) definition before ytcpsocket_pull.

azubala commented 4 years ago

@ermanitu thanks for the detailed investigation and the proposed fix. I faced the same issue with blocking ytcpsocket_pull and with your fix it runs flawlessly, well done 👍

For those who end up here with the same issue, you can check forked repo with @ermanitu fix here: https://github.com/azubala/SwiftSocket