Plattformübergreifender Multithread-TCP / IP-Server in C ++

Einmal bestand die Aufgabe darin, einen einfachen und schnellen Multithread-TCP / IP-Server in C ++ zu schreiben und gleichzeitig unter Windows und Linux zu arbeiten, ohne den Code außerhalb der Klasse des Servers selbst ändern zu müssen. Früher, in reinem C ++ ohne Bibliotheken wie Qt, hat der TCP-Server nicht geschrieben und sich selbst eine lange Zeit der Qual mit plattformabhängiger Abhängigkeit vorausgesagt. Es stellte sich jedoch heraus, dass alles viel einfacher ist, als es auf den ersten Blick schien, da die Socket-Schnittstellen beider Systeme im Grunde genommen zwei Wassertropfen ähneln und sich nur in kleinen Details unterscheiden.


Die Server- und Client-Klasse lautet also wie folgt:


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

Die Implementierung für Linux und Windows ist bis auf einige Stellen nahezu identisch. Dies wird nur durch verschiedene Strukturen verursacht, in denen Adressen ( struct sockaddr_in/SOCKADDR_IN, struct sockaddr/SOCKADDR) und Sockets ( int/SOCKET) gespeichert sind , sowie durch das Vorhandensein der Objektversion von WinSocket ( WSAData) in Windows .


Anwendungsbeispiel:


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


Gebrauchte Artikel:



All Articles