9

I have a chat application that has a separate thread to listen for incoming messages.

while (main thread not calling for receiver to quit) {
  string message = tcpCon.tcpReceive();    // Relies on the recv() function
  processIncomingMessage(message);
}

This way of working has one big problem. Most of the time, the loop will be blocking on recv() so the receiver thread won't quit. What would be a proper way to tackle this issue without forcing thread termination after a couple of seconds?

alk
  • 69,737
  • 10
  • 105
  • 255
Pieter
  • 31,619
  • 76
  • 167
  • 242
  • 1
    voted to reopen this because the linked duplicate is about winsock (Windows sockets) and the answers here are about sockets under Linux – Jim Hunziker Sep 09 '20 at 19:34

4 Answers4

12

Close the socket with shutdown() to close it for all receivers.

This prints out 'recv returned 0' on my system, indicating that the receiver saw an orderly shutdown. Comment out shutdown() and watch it hang forever.

Longer term, OP should fix the design, either using select or including an explicit quit message in the protocol.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

/* Free on my system. YMMV */
int port = 7777;
int cd;

void *f(void *arg)
{
    /* Hack: proper code would synchronize here */
    sleep(1);

    /* This works: */
    shutdown(cd, SHUT_RDWR);

    close(cd);
    return 0;
}

int main(void)
{
    /* Create a fake server which sends nothing */
    int sd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in sa = { 0 };
    const int on = 1;
    char buf;
    pthread_t thread;
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = htonl(INADDR_ANY);
    sa.sin_port = htons(port);
    setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);

    /* Other error reporting omitted for clarity */
    if (bind(sd, (const struct sockaddr*)&sa, sizeof sa) < 0) {
        perror("bind");
        return EXIT_FAILURE;
    }

    /* Create a client */
    listen(sd, 1);
    cd = socket(AF_INET, SOCK_STREAM, 0);
    connect(cd, (const struct sockaddr*)&sa, sizeof sa);
    accept(sd, 0, 0);

    /* Try to close socket on another thread */
    pthread_create(&thread, 0, f, 0);
    printf("recv returned %d\n", recv(cd, &buf, 1, 0));
    pthread_join(thread, 0);

    return 0;
}
fizzer
  • 13,551
  • 9
  • 39
  • 61
  • Is this different from doing `close(socketFD)`? – Pieter Nov 08 '11 at 15:56
  • @Pieter - yes. close() won't interrupt a thread blocked in a read. shutdown() closes for other readers, even in other processes. – fizzer Nov 09 '11 at 23:58
  • @EJP - See example code. recv returns 0 indicating orderly close. – fizzer Nov 10 '11 at 00:00
  • RE: including an explicit quit message. I can't, because I'm implementing the already-existing DCC sub-protocol. Am I correct in assuming that, although the `shutdown()` approach would answer my question in a more literal way, I should use `select()` with a certain timeout if I want to do it properly? – Pieter Nov 10 '11 at 17:48
  • Well, 'do it properly' is a matter of design, which is really just another way of saying it's a matter of opinion. Certainly, shutdown() on the reader end will solve your problem, and might be acceptable with a comment. It feels kind of dirty because morally you shouldn't have committed to blocking forever on a read without knowing it would complete. So what are the alternatives? If all the sockets do is carry chat to/from the GUI, I would service them all on the main thread in a single select loop which includes the listen socket(s). – fizzer Nov 10 '11 at 20:03
  • Assuming you are committed to a reader thread per connection, you still have choices. dgraves suggestion of calling select() on the single socket to test readability works. I haven't myself used SO_RECVTIMEO mentioned in his comment below, but it looks promising. Each of these has the downside of waiting until a timeout expires before polling the quit flag. This may not matter to your application. – fizzer Nov 10 '11 at 20:12
  • If you can rely on the peer behaving correctly, you might consider shutdown(SHUT_WR) - i.e. shutdown the writer end. The peer ought to read EOF and close its end of the socket. Then your recv() will in turn detect EOF and return 0. This seems a morally less reprehensible use of shutdown(). – fizzer Nov 10 '11 at 20:24
  • I'm gonna go with the SHUT_WR solution. Thanks. What do you mean with "peer behaving correctly"? You mean if the peer is using software that doesn't close its end of the socket when `recv()` returns `0`? – Pieter Nov 11 '11 at 09:48
5

You could use select() to wait for incoming data and avoid blocking in recv(). select() will also block, but you can have it time out after a set interval so that the while loop can continue and check for signals to quit from the main thread:

while (main thread not calling for receiver to quit) {
  if (tcpCon.hasData(500)) {              // Relies on select() to determine that data is
                                          // available; times out after 500 milliseconds
    string message = tcpCon.tcpReceive(); // Relies on the recv() function
    processIncomingMessage(message);
  }
}
dgraves
  • 351
  • 1
  • 2
  • 4
2

If you close the socket in another thread, then recv() will exit.

GazTheDestroyer
  • 20,722
  • 9
  • 70
  • 103
  • Is it thread-safe to call `close()` on a socket when a separate thread is blocking on a `recv()` for the same socket? – Pieter Nov 08 '11 at 15:55
  • Short answer: Yes. Long answer: Generally yes, but thread-safety depends on the OS and socket stack. Certain operations may cause problems but generally you wouldn't want to be doing them anyway (parallel reads on the same socket for example) – GazTheDestroyer Nov 08 '11 at 16:09
2

calling close on the socket from any other thread will make the recv call fail instantly.

stijn
  • 34,664
  • 13
  • 111
  • 163