Biblioteca de redes para 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);
     } 
};

Tengo una clase de redes que implementa funciones c de bajo nivel en una clase simple. Estoy buscando hacer mi código más eficiente.

Respuestas

3 MartinYork Oct 08 2020 at 07:05

Observación:

No me gusta el diseño.

Está utilizando una inicialización de dos fases. Construyes el objeto (por lo tanto, constructor) y luego llamas a una init()función. La inicialización en dos fases es una mala idea, ya que no puede saber (o tiene que validar) el estado del objeto antes de que pueda usarse.

Una mejor técnica es simplemente usar el constructor para inicializar el objeto. Si el constructor falla, entonces el lanzamiento para que el objeto nunca exista y no se pueda usar incorrectamente.

También tiene un close ( closeSocket()) explícito que está bien hasta cierto punto, pero está obligando a las personas a recordar llamar a esta función cuando haya terminado con el objeto. Para eso es un destructor.

Necesita buscar el concepto de RAII.

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

Diseño

¿De qué sirve connectToSite(), no es el init? ¿Puedes llamar a leer / escribir antes de llamar a esto? De lo contrario, la lectura / escritura está en el objeto incorrecto y la conexión debería devolverle el objeto apropiado que tiene la interfaz de lectura / escritura.


Así es como me gustaría usar un objeto llamado "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();
 }

Desde su interfaz. Buscaría cambiar el nombre de su clase a "Conexión", entonces las expectativas sobre cómo usar el objeto pueden ser más parecidas.

 {
    // 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();
 }

Revisión de código:

¿No quieres usar cadenas de C ++?

#include <string.h>

¿Una URL?

  int init(std::string url)

Seguro, pero el nombre de la clase es "Sitio web". Una URL es mucho más que un sitio web. Hay un esquema / host (nombre de host / puerto / usuario / contraseña) / ruta / consulta / fragmento. ¿No pasaría simplemente un host y para la conexión tal vez un puerto?

O puede pasar la URL completa, pero cada nueva conexión necesita una nueva URL, no solo el sitio web.


Está devolviendo el código de estado aquí (en caso de error):

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

Pero aquí estás volviendo -1

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

¿Está seguro de que esos rangos de error no se superponen?

Creo que una mejor interfaz sería lanzar una excepción si algo sale mal.


Esto no funcionará para la URL genérica.

  status = getaddrinfo(url.c_str(), "80" ....
  1. El puerto no siempre es 80.
  2. El puerto predeterminado está definido por el esquema "http / https ..."
  3. El valor predeterminado puede ser anulado por la propia URL.

Ejemplo:

  https://thorsanvil.com:1222/Work

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

Realmente el código anterior debería ser:

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

¿Qué pasa con las connect()fallas? ¿Espera que el usuario verifique el código de error?

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

Esa es una interfaz muy similar a C y muy propensa a usar errores. Sabemos que los usuarios de API no siempre verifican los códigos de error. No pierda esta ingormación. Hágalo explícito con una excepción.


No es así como se usa send()y recv()el valor de retorno indica cuánto del mensaje se envió / recibió. Debe llamar repetidamente a estas funciones hasta que se envíe el mensaje o haya un error.

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

Investigación

Eche un vistazo a esta clase:

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