1

I am trying to write client server application using udp protocol, but I have problem with connection ending.
I open two sockets (one is a "server" and the other is a "client"), and while the server receiving from the client with async, the client send him one simple message that printed to the console.
After some sleep (to be sure the server will call again to recv) the client and server socket getting closed. At this point I expected the recv will return -1 and the async will end.
But what actualy happen is that the recv stuck forever*.
If just before closing the socket I sending an empty package (sendToMakeExit variable set to true in the code), the recv return with that empty package, and only after next call it return -1, although the socket was closed in the first calling.

    const bool sendToMakeExit = false;
    const int port = 2000;
    const auto addr = "127.0.0.1";
    int serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    sockaddr_in target;
    target.sin_family = AF_INET;
    target.sin_port = htons(port);
    inet_pton(AF_INET, addr, &target.sin_addr);
    bind(serverSocket, (sockaddr *) &target, sizeof(target));
    auto readAsync = std::async(std::launch::async, [&serverSocket] {
        const int MAX_READ = 4096;
        char readBuf[MAX_READ];
        ssize_t actualRead;
        do {
            actualRead = recv(serverSocket, readBuf, MAX_READ, 0);
            if (actualRead > 0) {
                cout << readBuf << endl;
            }
        } while (actualRead != -1);
    });
    int clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    connect(clientSocket, (sockaddr *) &target, sizeof(target));
    this_thread::sleep_for(chrono::seconds(1));
    send(clientSocket, "test", 5, 0);
    this_thread::sleep_for(chrono::seconds(1));
    close(clientSocket);
    if (sendToMakeExit) {
        sendto(serverSocket, nullptr, 0, 0, (sockaddr *) &target, sizeof(target));
    }
    close(serverSocket);

*If I run this code in debug and create new breakpoint when the recv is stuck surprisingly the recv return with -1.
How can I getting the recv will return -1 when I close the socket?

amit
  • 177
  • 1
  • 13

1 Answers1

2

Closing a socket does not guarantee that any function call in another thread that is still using that socket immediately returns. If you have calls that wait for data to come in, like recv(), select(), poll(), you must send some data to the socket for these calls to return. You do that in your code, but you don't actually exit when a zero-length UDP packet is received: change the end of the while-loop to:

} while (actualRead > 0);

However, I would recommend having a flag variable that indicates whether the thread should continue running or not, like so:

volatile bool running = true;

auto readAsync = std::async(std::launch::async, [&serverSocket, &running] {
    ...
    while (running) {
        ...recv()...
    }
});

...
running = false;
sendto(serverSocket, ...);
readAsync.wait();
close(serverSocket);

Note that I added a line to wait for readAsync to finish before closing the socket, in order to prevent any accidents from happening: there is a small window where the socket is invalidated, but readAsync might still call recv() on it. If you have even more threads, it might also happen that you close the socket in this thread, another thread opens a new socket and gets the same filedescriptor number as the one you just closed, and then the readAsync thread would use the wrong socket.

G. Sliepen
  • 7,637
  • 1
  • 15
  • 31
  • "If you want the server to be notified when the client closes the UDP socket, you have to make the client send a packet that indicates that" - It's slightly more complicated than that. Since UDP is connectionless and delivery is not guaranteed, that "close" packet to the server might get lost and neither the server nor client would know that happened. So you need to design a protocol where each end acknowledge receipt and do re-sends if they don't get the expected replies (now you are close to implementing TCP). I suggest looking at [ENet](http://enet.bespin.org). – Jesper Juhl Sep 23 '18 at 14:03
  • I know that udp is connectionless and I know that when I close the client there is no data that send to the server. but here I also close the **server** socket - the one that the recv operate on. that is why I expect that after closing the socket of the recv, the function will return because the socket get closed. – amit Sep 23 '18 at 14:15
  • So I just need to always send data before closing the socket. Any idea why this behavior do not happen on windows? – amit Sep 23 '18 at 18:09
  • 1
    Maybe Windows does abort the `recv()` call when the socket is closed from another thread? There is nothing in the specifications for the `recv()` and `close()` functions that mention the behavior in a multi-threaded scenario, so it is up to the operating systems to decide how to handle it. Linux handles it by letting the `recv()` function wait indefinitely. See also: https://stackoverflow.com/a/6390101/5481471 – G. Sliepen Sep 23 '18 at 18:19