Biblioteca de rede para linux c ++

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

Eu tenho uma classe de rede que implementa funções c de baixo nível em uma classe simples. Estou procurando tornar meu código mais eficiente

Respostas

3 MartinYork Oct 08 2020 at 07:05

Observação:

Não gosto do design.

Você está usando uma inicialização de duas fases. Você constrói o objeto (portanto, o construtor) e, a seguir, chama uma init()função. A inicialização em duas fases é uma má ideia, pois você não pode dizer (ou tem que validar) o estado do objeto antes que ele possa ser usado.

Uma técnica melhor é simplesmente usar o construtor para inicializar o objeto. Se o construtor falhar, o lançamento para que o objeto nunca exista e não possa ser usado incorretamente.

Você também tem um close ( closeSocket()) explícito, o que é bom até certo ponto, mas você está forçando as pessoas a se lembrarem de chamar esta função quando terminar de usar o objeto. É para isso que serve um destruidor.

Você precisa pesquisar o conceito de RAII.

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

Projeto

Qual é a utilidade de connectToSite(), não é o init? Você pode ligar para leitura / gravação antes de ligar para isso? Caso contrário, a leitura / gravação está no objeto errado e a conexão deve retornar o objeto apropriado que possui a interface de leitura / gravação.


É assim que eu gostaria de usar um objeto chamado "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();
 }

De sua interface. Gostaria de renomear sua classe para "Conexão", então as expectativas de como usar o objeto podem ser mais semelhantes.

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

Revisão de código:

Você não quer usar strings C ++?

#include <string.h>

Um URL?

  int init(std::string url)

Claro, mas o nome da classe é "Site". Um url é muito mais do que apenas um website. Há um esquema / host (nome do host / porta / usuário / senha) / caminho / consulta / fragmento. Você não passaria apenas um host e para a conexão talvez uma porta.

Ou você pode passar o url inteiro, mas cada nova conexão precisa de um novo URL, não apenas do site.


Você está retornando o código de status aqui (em caso de erro):

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

Mas aqui está você reajustando -1

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

Tem certeza de que esses intervalos de erro não se sobrepõem?

Acho que uma interface melhor seria lançar uma exceção se algo der errado.


Isso não vai funcionar para o URL genérico.

  status = getaddrinfo(url.c_str(), "80" ....
  1. A porta nem sempre é 80.
  2. A porta padrão é definida pelo esquema "http / https ..."
  3. O padrão pode ser substituído pelo próprio url.

Exemplo:

  https://thorsanvil.com:1222/Work

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

Na verdade, o código acima deve ser:

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

O que acontece com connect()falha? Você espera que o usuário verifique o código de erro?

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

É uma interface muito semelhante a C e muito sujeita a erros. Sabemos que os usuários de API nem sempre verificam os códigos de erro. Não vaze esta ingormação. Torne-o explícito com uma exceção.


Não é assim que você usa send()e recv()o valor de retorno indica quanto da mensagem foi enviada / recebida. Você precisa chamar essas funções repetidamente até que a mensagem seja enviada ou até que ocorra um erro.

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

Pesquisa

Dê uma olhada nesta aula:

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