Servidor TCP / IP multiproceso multiplataforma en C ++

Una vez, surgió la tarea de escribir un servidor TCP / IP multiproceso simple y rápido en C ++ y al mismo tiempo trabajar desde Windows y Linux sin la necesidad de cambiar de alguna manera el código fuera de la clase del servidor. Anteriormente, en C ++ puro sin bibliotecas como Qt, el servidor Tcp no escribía, y presagiaba un largo tiempo de tormento con la dependencia de la plataforma. Pero resultó que todo es mucho más simple de lo que parecía a primera vista, porque básicamente las interfaces de socket de ambos sistemas son similares a dos gotas de agua y difieren solo en pequeños detalles.


Entonces la clase de servidor y cliente es la siguiente:


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

La implementación para Linux y Windows es casi idéntica, con la excepción de algunos lugares, causada solo por varias estructuras que almacenan direcciones ( struct sockaddr_in/SOCKADDR_IN, struct sockaddr/SOCKADDR) y sockets ( int/SOCKET), así como la presencia de una versión WinSocket ( WSAData) en Windows .


Ejemplo de uso:


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;
  }

}

Enlace GitHub


Artículos usados:



All Articles