Thư viện mạng cho 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);
     } 
};

Tôi có một lớp mạng thực thi các chức năng c cấp thấp trong một lớp đơn giản. Tôi đang tìm cách làm cho mã của mình hiệu quả hơn

Trả lời

3 MartinYork Oct 08 2020 at 07:05

Quan sát:

Tôi không thích thiết kế.

Bạn đang sử dụng khởi tạo hai giai đoạn. Bạn xây dựng đối tượng (do đó là hàm tạo) và sau đó gọi một init()hàm. Khởi tạo hai pha là một ý tưởng tồi vì bạn không thể biết (hoặc phải xác thực) trạng thái của đối tượng trước khi nó có thể được sử dụng.

Một kỹ thuật tốt hơn là chỉ cần sử dụng hàm tạo để khởi tạo đối tượng. Nếu hàm tạo không thành công thì ném để đối tượng không bao giờ tồn tại và không thể sử dụng sai.

Bạn cũng có một hàm close ( closeSocket()) rõ ràng ở một mức độ nào đó cũng tốt nhưng bạn đang buộc mọi người nhớ gọi hàm này khi bạn đã hoàn thành với đối tượng. Đây là những gì một trình hủy là cho.

Bạn cần tra cứu khái niệm RAII.

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

Thiết kế

Việc sử dụng connectToSite()là gì, đó không phải là init? Bạn có thể gọi đọc / ghi trước khi gọi cái này không? Nếu không thì đọc / ghi trên đối tượng sai và kết nối sẽ trả về cho bạn đối tượng thích hợp có giao diện đọc / ghi.


Đây là cách tôi muốn sử dụng một đối tượng có tên "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();
 }

Từ giao diện của bạn. Tôi muốn đổi tên lớp của bạn thành "Kết nối" thì kỳ vọng về cách sử dụng đối tượng có thể giống hơn.

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

Đánh giá mã:

Bạn không muốn sử dụng chuỗi C ++?

#include <string.h>

Một URL?

  int init(std::string url)

Chắc chắn nhưng tên của lớp là "Trang web". Url không chỉ là một trang web. Có một lược đồ / máy chủ lưu trữ (tên máy / cổng / người dùng / mật khẩu) / đường dẫn / truy vấn / phân mảnh. Bạn sẽ không chỉ vượt qua một máy chủ lưu trữ và kết nối có thể là một cổng.

Hoặc bạn có thể chuyển toàn bộ url nhưng mỗi kết nối mới cần một URL mới không chỉ trang web.


Bạn đang trả lại mã trạng thái ở đây (do lỗi):

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

Nhưng ở đây bạn đang thử nghiệm -1

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

Bạn có chắc chắn những phạm vi lỗi đó không trùng lặp không?

Tôi nghĩ rằng một giao diện tốt hơn sẽ là một ngoại lệ nếu có gì đó không ổn.


Điều này sẽ không hoạt động đối với URL chung.

  status = getaddrinfo(url.c_str(), "80" ....
  1. Cổng không phải lúc nào cũng là 80.
  2. Cổng mặc định được xác định bởi giản đồ "http / https ..."
  3. Mặc định có thể được ghi đè bởi chính url.

Thí dụ:

  https://thorsanvil.com:1222/Work

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

Thực sự đoạn mã trên phải là:

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

Điều gì xảy ra connect()không thành công? Bạn có mong đợi người dùng kiểm tra mã lỗi không?

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

Đó là giao diện rất giống C và rất dễ bị lỗi khi sử dụng. Chúng tôi biết rằng người dùng API không phải lúc nào cũng kiểm tra mã lỗi. Đừng để rò rỉ sự xâm nhập này. Làm cho nó rõ ràng với một ngoại lệ.


Đây không phải là cách bạn sử dụng send()và recv()giá trị trả về cho biết lượng tin nhắn đã được gửi / rút lại. Bạn cần gọi nhiều lần các chức năng này cho đến khi tin nhắn được gửi đi hoặc có lỗi.

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

Nghiên cứu

Hãy xem lớp học này:

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