Cross-platform multi-threaded TCP / IP server in C ++

Once, the task arose of writing a simple and fast multi-threaded TCP / IP server in C ++ and at the same time to work from under Windows and Linux without the need to somehow change the code outside the class of the server itself. Previously, in pure C ++ without libraries like Qt, the Tcp server did not write, and foreshadowed itself a long time of torment with platform-dependent. But as it turned out, everything is much simpler than it seemed at first glance, because basically the socket interfaces of both systems are similar as two drops of water and differ only in small details.


So the server and client class is as follows:


TcpServer.h


#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <cstdint>
#include <functional>
#include <thread>
#include <list>

#ifdef _WIN32 // Windows NT

#include <WinSock2.h>

#else // *nix

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

#endif

//     
static constexpr uint16_t buffer_size = 4096;

struct TcpServer {
    class Client;
     // Callback-  
    typedef std::function<void(Client)> handler_function_t;
     // 
    enum class status : uint8_t {
        up = 0,
        err_socket_init = 1,
        err_socket_bind = 2,
        err_socket_listening = 3,
        close = 4
    };

private:
    uint16_t port; //
    status _status = status::close;
    handler_function_t handler;

    std::thread handler_thread;
    std::list<std::thread> client_handler_threads;
    std::list<std::thread::id> client_handling_end;

#ifdef _WIN32 // Windows NT
    SOCKET serv_socket = INVALID_SOCKET;
    WSAData w_data;
#else // *nix
    int serv_socket;
#endif

    void handlingLoop();

public:
    TcpServer(const uint16_t port, handler_function_t handler);
    ~TcpServer();

    //! Set client handler
    void setHandler(handler_function_t handler);

    uint16_t getPort() const;
    uint16_t setPort(const uint16_t port);

    status getStatus() const {return _status;}

    status restart();
    status start();
    void stop();

    void joinLoop();
};

class TcpServer::Client {
#ifdef _WIN32 // Windows NT
    SOCKET socket;
    SOCKADDR_IN address;
    char buffer[buffer_size];
public:
    Client(SOCKET socket, SOCKADDR_IN address);
#else // *nix
    int socket;
    struct sockaddr_in address;
    char buffer[buffer_size];
public:
    Client(int socket, struct sockaddr_in address);
#endif
public:
    Client(const Client& other);
    ~Client();
    uint32_t getHost() const;
    uint16_t getPort() const;

    int loadData();
    char* getData();

    bool sendData(const char* buffer, const size_t size) const;
};

#endif // TCPSERVER_H

SOCKET Windows ( ) int Linux. Linux int , Windows , :


//file _socket_types.h
//...
#if 1
typedef UINT_PTR    SOCKET;
#else
typedef INT_PTR     SOCKET;
#endif
//...

//file BaseTsd.h
//...
#if defined(_WIN64)
 typedef unsigned __int64 UINT_PTR;
#else
 typedef unsigned int UINT_PTR;
#endif
//...
#if defined(_WIN64) 
 typedef __int64 INT_PTR; 
#else 
 typedef int INT_PTR;
#endif
//...

Windows TcpServer- WinSocket — WSAData w_data;(. WSAData)


:


TcpServer.cpp


#include "../hdr/TcpServer.h"
#include <chrono>

// :
//port -      
//handler - callback-    
//                 callback
//          ( -: [](TcpServer::Client){...do something...})
TcpServer::TcpServer(const uint16_t port, handler_function_t handler) : port(port), handler(handler) {}

//      
//    WinSocket
TcpServer::~TcpServer() {
  if(_status == status::up)
    stop();
#ifdef _WIN32 // Windows NT
    WSACleanup ();
#endif
}

// callback-    
void TcpServer::setHandler(TcpServer::handler_function_t handler) {this->handler = handler;}

//Getter/Setter 
uint16_t TcpServer::getPort() const {return port;}
uint16_t TcpServer::setPort( const uint16_t port) {
    this->port = port;
    restart(); //    
    return port;
}

// 
TcpServer::status TcpServer::restart() {
    if(_status == status::up)
      stop ();
    return start();
}

//     
void TcpServer::joinLoop() {handler_thread.join();}

//         
int TcpServer::Client::loadData() {return recv(socket, buffer, buffer_size, 0);}
//       
char* TcpServer::Client::getData() {return buffer;}
//  
bool TcpServer::Client::sendData(const char* buffer, const size_t size) const {
  if(send(socket, buffer, size, 0) < 0) return false;
  return true;
}

#ifdef _WIN32 // Windows NT
// 
TcpServer::status TcpServer::start() {
    WSAStartup(MAKEWORD(2, 2), &w_data); //  WinSocket

    SOCKADDR_IN address; // //   
    address.sin_addr.S_un.S_addr = INADDR_ANY; // IP 
    address.sin_port = htons(port); // 
    address.sin_family = AF_INET; //AF_INET - C   IPv4

    //        
    //      
    if(static_cast<int>(serv_socket = socket(AF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR) return _status = status::err_socket_init;
    //          
    //      
    if(bind(serv_socket, (struct sockaddr*)&address, sizeof(address)) == SOCKET_ERROR) return _status = status::err_socket_bind;
    //      
    //      
    if(listen(serv_socket, SOMAXCONN) == SOCKET_ERROR) return _status = status::err_socket_listening;

    // ,      
    _status = status::up;
    handler_thread = std::thread([this]{handlingLoop();});
    return _status;
}

// 
void TcpServer::stop() {
    _status = status::close; // 
    closesocket (serv_socket); // 
    joinLoop(); // 
    for(std::thread& cl_thr : client_handler_threads) //   
        cl_thr.join(); //   
    client_handler_threads.clear (); //    
    client_handling_end.clear (); //      
}

//   
void TcpServer::handlingLoop() {
    while(_status == status::up) {
        SOCKET client_socket; // 
        SOCKADDR_IN client_addr; // 
        int addrlen = sizeof(client_addr); //  
        //    
        //(        )
        if ((client_socket = accept(serv_socket, (struct sockaddr*)&client_addr, &addrlen)) != 0 && _status == status::up){
            client_handler_threads.push_back(std::thread([this, &client_socket, &client_addr] {
                handler(Client(client_socket, client_addr)); // callback-
                //       
                client_handling_end.push_back (std::this_thread::get_id()); 
            }));
        }
        //   
        if(!client_handling_end.empty())
          for(std::list<std::thread::id>::iterator id_it = client_handling_end.begin (); !client_handling_end.empty() ; id_it = client_handling_end.begin())
            for(std::list<std::thread>::iterator thr_it = client_handler_threads.begin (); thr_it != client_handler_threads.end () ; ++thr_it)
              if(thr_it->get_id () == *id_it) {
                thr_it->join();
                client_handler_threads.erase(thr_it);
                client_handling_end.erase (id_it);
                break;
              }

        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

//      
TcpServer::Client::Client(SOCKET socket, SOCKADDR_IN address) : socket(socket), address(address) {}
//  
TcpServer::Client::Client(const TcpServer::Client& other) : socket(other.socket), address(other.address) {}

TcpServer::Client::~Client() {
    shutdown(socket, 0); //  
    closesocket(socket); // 
}

//    
uint32_t TcpServer::Client::getHost() const {return address.sin_addr.S_un.S_addr;}
uint16_t TcpServer::Client::getPort() const {return address.sin_port;}

#else // *nix

//  (     Windows)
TcpServer::status TcpServer::start() {
    struct sockaddr_in server;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons( port );
    server.sin_family = AF_INET;
    serv_socket = socket(AF_INET, SOCK_STREAM, 0);

    if(serv_socket == -1) return _status = status::err_socket_init;
    if(bind(serv_socket,(struct sockaddr *)&server , sizeof(server)) < 0) return _status = status::err_socket_bind;
    if(listen(serv_socket, 3) < 0)return _status = status::err_socket_listening;

    _status = status::up;
    handler_thread = std::thread([this]{handlingLoop();});
    return _status;
}

// 
void TcpServer::stop() {
    _status = status::close;
    close(serv_socket);
    joinLoop();
    for(std::thread& cl_thr : client_handler_threads)
        cl_thr.join();
    client_handler_threads.clear ();
    client_handling_end.clear ();
}

//    (     Windows)
void TcpServer::handlingLoop() {
    while (_status == status::up) {
        int client_socket;
        struct sockaddr_in client_addr;
        int addrlen = sizeof (struct sockaddr_in);
        if((client_socket = accept(serv_socket, (struct sockaddr*)&client_addr, (socklen_t*)&addrlen)) >= 0 && _status == status::up)
            client_handler_threads.push_back(std::thread([this, &client_socket, &client_addr] {
                handler(Client(client_socket, client_addr));
                client_handling_end.push_back (std::this_thread::get_id());
            }));

        if(!client_handling_end.empty())
          for(std::list<std::thread::id>::iterator id_it = client_handling_end.begin (); !client_handling_end.empty() ; id_it = client_handling_end.begin())
            for(std::list<std::thread>::iterator thr_it = client_handler_threads.begin (); thr_it != client_handler_threads.end () ; ++thr_it)
              if(thr_it->get_id () == *id_it) {
                thr_it->join();
                client_handler_threads.erase(thr_it);
                client_handling_end.erase (id_it);
                break;
              }

        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

//      
TcpServer::Client::Client(int socket, struct sockaddr_in address) : socket(socket), address(address) {}
//  
TcpServer::Client::Client(const TcpServer::Client& other) : socket(other.socket), address(other.address) {}

TcpServer::Client::~Client() {
    shutdown(socket, 0); //  
    close(socket); // 
}

//    
uint32_t TcpServer::Client::getHost() {return address.sin_addr.s_addr;}
uint16_t TcpServer::Client::getPort() {return address.sin_port;}

#endif

The implementation for Linux and Windows is almost identical, with the exception of some places, caused only by various structures that store addresses ( struct sockaddr_in/SOCKADDR_IN, struct sockaddr/SOCKADDR) and sockets ( int/SOCKET), as well as the presence of a WinSocket ( WSAData) version in Windows .


Usage example:


main.cpp


#include "server/hdr/TcpServer.h"

#include <iostream>

// ip  std::string
std::string getHostStr(const TcpServer::Client& client) {
  uint32_t ip = client.getHost ();
  return std::string() + std::to_string(int(reinterpret_cast<char*>(&ip)[0])) + '.' +
         std::to_string(int(reinterpret_cast<char*>(&ip)[1])) + '.' +
         std::to_string(int(reinterpret_cast<char*>(&ip)[2])) + '.' +
         std::to_string(int(reinterpret_cast<char*>(&ip)[3])) + ':' +
         std::to_string( client.getPort ());
}

int main() {
  //  TcpServer      -   
  TcpServer server( 8080,

      [](TcpServer::Client client){

          //     
          std::cout<<"Connected host:"<<getHostStr(client)<<std::endl;

          //   
          int size = 0;
          while (size == 0) size = client.loadData ();

          //       
          std::cout
              <<"size: "<<size<<" bytes"<<std::endl
              << client.getData() << std::endl;

          //  
          const char answer[] = "Hello World from Server";
          client.sendData(answer, sizeof (answer));
      }

  );

  // 
  if(server.start() == TcpServer::status::up) {
    //          
      std::cout<<"Server is up!"<<std::endl;
      server.joinLoop();
  } else {
    //         
      std::cout<<"Server start error! Error code:"<< int(server.getStatus()) <<std::endl;
      return -1;
  }

}

GitHub Link


Used articles:



All Articles