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

Sekali, tugas muncul dari menulis server TCP / IP multi-threaded sederhana dan cepat di C ++ dan pada saat yang sama bekerja dari bawah Windows dan Linux tanpa perlu entah bagaimana mengubah kode di luar kelas server itu sendiri. Sebelumnya, di C + + murni tanpa pustaka seperti Qt, server Tcp tidak menulis, dan meramalkan sendiri siksaan lama dengan platform-dependent. Tapi ternyata, semuanya jauh lebih sederhana daripada yang terlihat pada pandangan pertama, karena pada dasarnya antarmuka soket kedua sistem sama seperti dua tetes air dan hanya berbeda dalam detail kecil.


Jadi kelas server dan klien adalah sebagai berikut:


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

Implementasi untuk Linux dan Windows hampir identik, dengan pengecualian beberapa tempat, hanya disebabkan oleh berbagai struktur yang menyimpan alamat ( struct sockaddr_in/SOCKADDR_IN, struct sockaddr/SOCKADDR) dan soket ( int/SOCKET), serta kehadiran versi WinSocket ( WSAData) di Windows .


Contoh penggunaan:


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

}

Tautan GitHub


Artikel bekas:



All Articles