Biblioteka sieciowa dla c ++ linux
#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);
}
};
Mam klasę sieciową, która implementuje funkcje niskiego poziomu c w prostej klasie. Chcę, aby mój kod był bardziej wydajny
Odpowiedzi
Obserwacja:
Nie podoba mi się projekt.
Używasz inicjalizacji dwufazowej. Konstruujesz obiekt (czyli konstruktor), a następnie wywołujesz init()funkcję. Inicjalizacja dwufazowa to zły pomysł, ponieważ nie możesz powiedzieć (lub musisz zweryfikować) stanu obiektu, zanim będzie można go użyć.
Lepszą techniką jest po prostu użycie konstruktora do zainicjowania obiektu. Jeśli konstruktor się nie powiedzie, rzut, więc obiekt nigdy nie istnieje i nie można go niepoprawnie użyć.
Masz również jawną metodę close ( closeSocket()), która jest w porządku do pewnego stopnia, ale zmuszasz ludzi do pamiętania o wywołaniu tej funkcji, gdy skończysz z obiektem. Do tego służy destruktor.
Musisz przyjrzeć się koncepcji RAII.
{
Website site("Stuff"); // All resources allocated
}
// All resources for `site` are now released.
Projekt
Jaki jest pożytek connectToSite(), czy to nie init? Czy możesz zadzwonić do odczytu / zapisu przed wywołaniem tego? Jeśli nie, to odczyt / zapis znajdują się na niewłaściwym obiekcie i connect powinno zwrócić odpowiedni obiekt z interfejsem odczytu / zapisu.
Oto jak chciałbym użyć obiektu o nazwie „Witryna internetowa”
{
// 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();
}
Z Twojego interfejsu. Chciałbym zmienić nazwę Twojej klasy na „Połączenie”, wtedy oczekiwania co do sposobu korzystania z obiektu mogą być bardziej podobne.
{
// 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();
}
Przegląd kodu:
Nie chcesz używać ciągów C ++?
#include <string.h>
URL?
int init(std::string url)
Jasne, ale nazwa klasy to „Witryna”. Adres URL to znacznie więcej niż tylko witryna internetowa. Istnieje schemat / host (nazwa hosta / port / użytkownik / hasło) / ścieżka / zapytanie / fragment. Czy nie przekazałbyś po prostu hosta, a do połączenia może portu.
Lub możesz podać cały adres URL, ale każde nowe połączenie wymaga nowego adresu URL, a nie tylko witryny.
Tutaj zwracasz kod stanu (w przypadku błędu):
if((status = getaddrinfo(url.c_str(), "80", &hints, &servinfo)) != 0){
return status;
}
Ale tutaj wracasz -1
if((sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == -1){
return -1;
}
Czy na pewno te zakresy błędów się nie pokrywają?
Myślę, że lepszym interfejsem byłoby zgłoszenie wyjątku, jeśli coś pójdzie nie tak.
To nie zadziała w przypadku ogólnego adresu URL.
status = getaddrinfo(url.c_str(), "80" ....
- Port nie zawsze ma 80 lat.
- Domyślny port jest definiowany przez schemat „http / https ...”
- Wartość domyślna może zostać zastąpiona przez sam adres URL.
Przykład:
https://thorsanvil.com:1222/Work
Schema: https
Site: thorsanvil.com
Port: 1222 overrides default 443 for https
Path: /Work
Query: Empty
Fragment: Empty
Naprawdę powyższy kod powinien być:
status = getaddrinfo(getHost(url), getPort(url), ....
Co się dzieje z connect()niepowodzeniem? Czy oczekujesz, że użytkownik sprawdzi kod błędu?
int connectToSite(){
return connect(sock, servinfo->ai_addr, servinfo->ai_addrlen);
}
Jest to interfejs bardzo podobny do C i bardzo podatny na błędy w użyciu. Wiemy, że użytkownicy API nie zawsze sprawdzają kody błędów. Nie ujawniaj tego infekcji. Wyraź to wyraźnie z wyjątkiem.
To nie jest sposób, w jaki używasz, send()a recv()wartość zwracana wskazuje, jaka część wiadomości została wysłana / odzyskana. Musisz wielokrotnie wywoływać te funkcje, aż wiadomość zostanie wysłana lub wystąpi błąd.
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);
}
Badania
Spójrz na tę klasę:
https://github.com/Loki-Astari/ThorsSocket/blob/master/src/ThorsSocket/Socket.h