Libreria di rete per c ++ linux

Oct 08 2020
#include <sys/types.h>
#include <sys/socket.h>

#include <netdb.h>
#include <string.h>
#include <unistd.h>

#include <string>
class Website
{
    int status, sock;
    struct addrinfo hints;
    struct addrinfo *servinfo;
    public:
      int init(std::string url){
          memset(&hints, 0, sizeof hints);
          hints.ai_family = AF_UNSPEC;
          hints.ai_socktype = SOCK_STREAM;
      if((status = getaddrinfo(url.c_str(), "80", &hints, &servinfo)) != 0){
              return status;
          }
          if((sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1){
              return -1;
      }
          return 0;
       } 
     int connectToSite(){
        return connect(sock, servinfo->ai_addr, servinfo->ai_addrlen);
     }
     int sendToSite(std::string request){
        return send(sock, request.c_str(), strlen(request.c_str()), 0);
     }
     int recvFromSite(char buf[], int maxsize){
        return recv(sock, buf, maxsize, 0);
     }
    void closeSocket(){
      close(sock);
      freeaddrinfo(servinfo);
     } 
};

Ho una classe di rete che implementa funzioni c di basso livello in una classe semplice. Sto cercando di rendere il mio codice più efficiente

Risposte

3 MartinYork Oct 08 2020 at 07:05

Osservazione:

Non mi piace il design.

Stai usando un'inizializzazione a due fasi. Costruisci l'oggetto (quindi il costruttore) e quindi chiami una init()funzione. L'inizializzazione in due fasi è una cattiva idea in quanto non è possibile dire (o dover convalidare) lo stato dell'oggetto prima che possa essere utilizzato.

Una tecnica migliore consiste nell'usare semplicemente il costruttore per inizializzare l'oggetto. Se il costruttore fallisce, il lancio quindi l'oggetto non esiste mai e non può essere utilizzato in modo errato.

Hai anche un close ( closeSocket()) esplicito che va bene in una certa misura, ma costringi le persone a ricordarsi di chiamare questa funzione quando hai finito con l'oggetto. A questo serve un distruttore.

Devi cercare il concetto di RAII.

{
    Website    site("Stuff"); // All resources allocated
}
// All resources for `site` are now released.

Design

A cosa serve connectToSite(), non è quello l'init? Puoi chiamare lettura / scrittura prima di chiamare questo? In caso contrario, le operazioni di lettura / scrittura si trovano sull'oggetto sbagliato e la connessione dovrebbe restituirti l'oggetto appropriato che ha l'interfaccia di lettura / scrittura.


Questo è il modo in cui vorrei utilizzare un oggetto chiamato "WebSite"

{
    // An abstract object that represents the site.
    // No actual underlying connection as connections are
    // usually to resources on the site not the actual site.
    Website    site("thorsanvil.com", 80);

    // Create a connection to a specific resource on the site.
    Connection c = site.open("Path");

    // Object was created successfully so we have
    // a valid connection to a server we can not communicate with.
    c.writeData("This is a message");
    std::string resp = c.read();
 }

Dalla tua interfaccia. Cercherei di rinominare la tua classe in "Connection", quindi le aspettative su come utilizzare l'oggetto potrebbero essere più simili.

 {
    // Create the socket and connect to the site.
    Connection   c("https://thorsanvil.com/MyResource");

    // Object was created successfully so we have
    // a valid connection to a server we can not communicate with.
    c.writeData("This is a message");
    std::string response = c.readData();
 }

Revisione del codice:

Non vuoi usare stringhe C ++?

#include <string.h>

Un URL?

  int init(std::string url)

Certo ma il nome della classe è "Website". Un URL è molto più di un semplice sito web. C'è uno schema / host (nome host / porta / utente / password) / percorso / query / frammento. Non passeresti semplicemente un host e per la connessione forse una porta.

Oppure potresti passare l'intero URL ma ogni nuova connessione necessita di un nuovo URL non solo del sito web.


Stai restituendo il codice di stato qui (in caso di errore):

  if((status = getaddrinfo(url.c_str(), "80", &hints, &servinfo)) != 0){
          return status;
      }

Ma qui stai risintonizzando -1

      if((sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1){
          return -1;
      }

Sei sicuro che questi intervalli di errore non si sovrappongano?

Penso che un'interfaccia migliore sarebbe lanciare un'eccezione se qualcosa va storto.


Questo non funzionerà per l'URL generico.

  status = getaddrinfo(url.c_str(), "80" ....
  1. La porta non è sempre 80.
  2. La porta predefinita è definita dallo schema "http / https ..."
  3. L'impostazione predefinita può essere sovrascritta dall'URL stesso.

Esempio:

  https://thorsanvil.com:1222/Work

  Schema:   https
  Site:     thorsanvil.com
  Port:     1222             overrides default 443 for https
  Path:     /Work
  Query:    Empty
  Fragment: Empty

In realtà il codice sopra dovrebbe essere:

  status = getaddrinfo(getHost(url), getPort(url), ....

Cosa succede se connect()fallisce? Ti aspetti che l'utente controlli il codice di errore?

 int connectToSite(){
    return connect(sock, servinfo->ai_addr, servinfo->ai_addrlen);
 }

È un'interfaccia molto simile a C e molto soggetta a errori da usare. Sappiamo che gli utenti dell'API non controllano sempre i codici di errore. Non perdere questa ingormazione. Rendilo esplicito con un'eccezione.


Non è così che usi send()e recv()il valore restituito indica la quantità di messaggio inviata / ricevuta. È necessario chiamare ripetutamente queste funzioni finché il messaggio non viene inviato o si verifica un errore.

 int sendToSite(std::string request){
    return send(sock, request.c_str(), strlen(request.c_str()), 0);
 }
 int recvFromSite(char buf[], int maxsize){
    return recv(sock, buf, maxsize, 0);
 }

Ricerca

Dai un'occhiata a questo corso:

https://github.com/Loki-Astari/ThorsSocket/blob/master/src/ThorsSocket/Socket.h