Search code examples
c++multithreadingtcpwinsockwinsock2

Simple non-blocking multi-threaded tcp server


I'm studying C++, and this weekend I started to play around with sockets and threads. Bellow is a simple multi threaded server that I'm making based on some tutorials.

The issue that I'm facing is that when I'm connecting with 2 telnet clients only the keystrokes form the first connection appear on the server. Any keystroke sent from the second telnet connection appears suddenly once the first telnet connection closes. Could someone explain to me what have I done wrong here?

#include <iostream>
#include <string>
#include <thread>
#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment (lib, "ws2_32.lib")

void clientSocketHandler(SOCKET clientSocket, std::string client_ip) {

    char buf[4096];

    std::thread::id thread_id = std::this_thread::get_id();
    std::cout << thread_id << " - " << client_ip << ": connected" << std::endl;

    while (true)
    {

        ZeroMemory(buf, 4096);

        int bytesReceived = recv(clientSocket, buf, 4096, 0);

        if (bytesReceived == 0)
        {

            std::cout << thread_id << " - " << client_ip << ": disconnected" << std::endl;

            break;

        }

        if (bytesReceived > 0) 
        {

            std::cout << thread_id << " - " << client_ip << ": " << std::string(buf, 0, bytesReceived) << std::endl;

            //send(clientSocket, buf, bytesReceived + 1, 0);

        }

    }

    std::cout << thread_id << " - " << client_ip << ": closing client socket & exiting thread..." << std::endl;

    closesocket(clientSocket);

}

void waitForConnections(SOCKET serverSocket) {

    sockaddr_in hint;

    hint.sin_family = AF_INET;
    hint.sin_port = htons(1337);
    hint.sin_addr.S_un.S_addr = INADDR_ANY;

    bind(serverSocket, (sockaddr*)&hint, sizeof(hint));
    listen(serverSocket, SOMAXCONN);

    while (true) {

        sockaddr_in client;

        int clientSize = sizeof(client);

        SOCKET clientSocket = accept(serverSocket, (sockaddr*)&client, &clientSize);

        if (clientSocket != INVALID_SOCKET) 
        {

            char host[NI_MAXHOST];      // Client's remote name

            ZeroMemory(host, NI_MAXHOST); // same as memset(host, 0, NI_MAXHOST);

            std::string client_ip = inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
            std::thread t(clientSocketHandler, clientSocket, client_ip);

            t.join();

        }

        Sleep(100);

    }

}

int main()
{
    // Initialze winsock
    WSADATA wsData;
    WORD ver = MAKEWORD(2, 2);

    int wsOk = WSAStartup(ver, &wsData);

    if (wsOk != 0)
    {
        std::cerr << "Can't Initialize winsock! Quitting..." << std::endl;

        return 1;
    }

    // Create a socket
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (serverSocket == INVALID_SOCKET)
    {
        WSACleanup();
        std::cerr << "Can't create a socket! Quitting..." << std::endl;

        return 1;
    }

    // If serverSocketMode = 0, blocking is enabled; 
    // If serverSocketMode != 0, non-blocking mode is enabled.
    u_long serverSocketMode = 1;

    if (ioctlsocket(serverSocket, FIONBIO, &serverSocketMode) != NO_ERROR) 
    {
        WSACleanup();
        std::cerr << "Can't set socket to non-blocking mode! Quitting..." << std::endl;

        return 1;
    }

    // Disables the Nagle algorithm for send coalescing.
    // This socket option is included for backward 
    // compatibility with Windows Sockets 1.1
    BOOL flag = TRUE;

    if (setsockopt(serverSocket, IPPROTO_TCP, TCP_NODELAY, (const char *)&flag, sizeof(flag)) != NO_ERROR)
    {
        WSACleanup();
        std::cerr << "Can't set socket NO_DELAY option! Quitting..." << std::endl;

        return 1;
    }

    // Start listening for connections
    waitForConnections(serverSocket);

    // Cleanup winsock
    WSACleanup();

    system("pause");

    return 0;

}

Solution

  • This should work. I removed pointless things like setting the socket to non-blocking and disabling the Nagle algorithm. The latter should only be done for things that need low-millisecond interactivity.

    But, the substantial change that should fix your problem is changing join to detach. Using join causes your program to wait for the thread to finish before continuing. Using detach says "This thread is going to run in the background doing things, and I don't care about learning its fate later.".

    If you don't use one of the two, and the ::std::thread object is destroyed, the system throws an exception because you're destroying the only means you have of getting information about whether or not a thread exited with an error of some kind with saying that either you don't care about such information, or explicitly asking for it.

    I don't have Windows, so I can't test it:

    #include <iostream>
    #include <string>
    #include <thread>
    #include <winsock2.h>
    #include <ws2tcpip.h>
    
    #pragma comment (lib, "ws2_32.lib")
    
    void clientSocketHandler(SOCKET clientSocket, std::string client_ip)
    {
       char buf[4096];
    
       std::thread::id thread_id = std::this_thread::get_id();
       std::cout << thread_id << " - " << client_ip << ": connected" << std::endl;
    
       while (true)
       {
    
          ZeroMemory(buf, 4096);
    
          int bytesReceived = recv(clientSocket, buf, 4096, 0);
    
          if (bytesReceived == 0)
          {
    
             std::cout << thread_id << " - " << client_ip << ": disconnected" << std::endl;
    
             break;
    
          }
    
          if (bytesReceived > 0)
          {
    
             std::cout << thread_id << " - " << client_ip << ": " << std::string(buf, 0, bytesReceived) << std::endl;
    
             //send(clientSocket, buf, bytesReceived + 1, 0);
    
          }
    
       }
    
       std::cout << thread_id << " - " << client_ip << ": closing client socket & exiting thread..." << std::endl;
    
       closesocket(clientSocket);
    
    }
    
    void waitForConnections(SOCKET serverSocket)
    {
    
       sockaddr_in hint;
    
       hint.sin_family = AF_INET;
       hint.sin_port = htons(1337);
       hint.sin_addr.S_un.S_addr = INADDR_ANY;
    
       bind(serverSocket, (sockaddr*)&hint, sizeof(hint));
       listen(serverSocket, SOMAXCONN);
    
       while (true) {
    
          sockaddr_in client;
    
          int clientSize = sizeof(client);
    
          SOCKET clientSocket = accept(serverSocket, (sockaddr*)&client, &clientSize);
    
          if (clientSocket != INVALID_SOCKET)
          {
    
             char host[NI_MAXHOST];      // Client's remote name
    
             ZeroMemory(host, NI_MAXHOST); // same as memset(host, 0, NI_MAXHOST);
    
             std::string client_ip = inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
             std::thread t(clientSocketHandler, clientSocket, client_ip);
    
             t.detach();
    
          }
    
          Sleep(100);
    
       }
    
    }
    
    int main()
    {
        // Initialze winsock
        WSADATA wsData;
        WORD ver = MAKEWORD(2, 2);
    
        int wsOk = WSAStartup(ver, &wsData);
    
        if (wsOk != 0)
        {
            std::cerr << "Can't Initialize winsock! Quitting..." << std::endl;
    
            return 1;
        }
    
        // Create a socket
        SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
        if (serverSocket == INVALID_SOCKET)
        {
            WSACleanup();
            std::cerr << "Can't create a socket! Quitting..." << std::endl;
    
            return 1;
        }
    
        // Start listening for connections
        waitForConnections(serverSocket);
    
        // Cleanup winsock
        WSACleanup();
    
        system("pause");
    
        return 0;
    
    }