Myhttp封装http协议

源代码

#include <iostream>
#include <cstring>
#include <string>
#include <thread>
#include <atomic>
#include <fstream> // 添加文件操作头文件

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含 inet_pton 函数
#define closesocket close
#endif

struct hostent *AutoGetIp(std::string domain, std::string &target_ip)
{
    struct hostent *host = gethostbyname(domain.c_str());
    if (host == nullptr)
    {
        std::cerr << "Get IP address error for domain: " << domain << std::endl;
        return nullptr;
    }
    std::cout << "Initialized domain to IP: " << host->h_name;
    if (host->h_addr_list[0])
    {
        target_ip = inet_ntoa(*(struct in_addr *)host->h_addr_list[0]); // 将域名返回的可用ip列表第一个赋值给ip
        std::cout << " --> " << target_ip << std::endl;
    }
    return host;
}

class HttpRequest
{
private:
    int client_fd = -1;
    std::atomic<bool> stopListening{false};

public:
    struct hostent *host; // 主机结构体
    std::string domain;
    std::string ip;
    int ip_version = AF_INET; // 默认使用ipv4
    int port = 80;            // 默认http协议是80端口

    HttpRequest() : host(nullptr) {}

    HttpRequest(std::string domain_) : HttpRequest(domain_, 80) {}

    HttpRequest(std::string domain_, int port_) : domain(domain_), port(port_)
    {
        std::string target_ip;
        host = AutoGetIp(domain, target_ip);
        if (host == nullptr)
        {
            std::cerr << "Failed to get IP address for domain: " << domain << std::endl;
            return;
        }
        ip = target_ip;
        ip_version = host->h_addrtype;
        std::string ip_str = ip_version == AF_INET ? "IPv4" : "IPv6";
        std::cout << "IP version: " << ip_str << std::endl;
    }

    HttpRequest(std::string ip_, int ip_v, int port_) : ip(ip_), ip_version(ip_v), port(port_) // 如果只知道ip和ip协议的版本
    {
        struct in_addr addr; // ipv4地址结构体
        if (inet_pton(ip_version, ip.c_str(), &addr) <= 0)
        {
            std::cerr << "Invalid address/ Address not supported." << std::endl;
            return;
        }

        host = gethostbyaddr((const char *)&addr, sizeof(addr), ip_version);
        if (host == nullptr)
        {
            std::cerr << "Get host by address error for IP: " << ip << std::endl;
            return;
        }
        domain = host->h_name;
        printf("Hostname: %s\n", host->h_name);
    }

    ~HttpRequest()
    {
        stopListening = true;
        if (client_fd != -1)
        {
            closesocket(client_fd);
        }
#ifdef _WIN32
        WSACleanup();
#endif
    }

    void setIP(std::string ip_) { ip = ip_; }
    void setPort(int port_) { port = port_; }

    int connect_to()
    {
#ifdef _WIN32
        WSADATA wsaData;
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        {
            std::cerr << "Failed to initialize Winsock." << std::endl;
            return -1;
        }
#endif

        client_fd = socket(ip_version, SOCK_STREAM, 0);
        if (client_fd == INVALID_SOCKET)
        {
            std::cerr << "Socket creation failed." << std::endl;
#ifdef _WIN32
            WSACleanup();
#endif
            return -1;
        }

        struct sockaddr_in server_address;
        server_address.sin_family = AF_INET; // 使用 IPv4 协议
        server_address.sin_port = htons(port);

        if (inet_pton(ip_version, ip.c_str(), &server_address.sin_addr) <= 0)
        {
            std::cerr << "Invalid address/ Address not supported." << std::endl;
            closesocket(client_fd);
#ifdef _WIN32
            WSACleanup();
#endif
            return -1;
        }

        if (connect(client_fd, (struct sockaddr *)&server_address, sizeof(server_address)) < 0)
        {
            std::cerr << "Connection failed." << std::endl;
            closesocket(client_fd);
#ifdef _WIN32
            WSACleanup();
#endif
            return -1;
        }

        std::cout << "Connected to server." << std::endl;

        sendContent();

        std::ofstream outFile("server_response.txt", std::ios::app); // 创建或打开文件以追加内容
        if (!outFile.is_open())
        {
            std::cerr << "Failed to open file for writing." << std::endl;
            return -1;
        }

        std::cout << "File opened successfully: server_response.txt" << std::endl;

        std::thread t([this, &outFile]()
                      {
                          char buffer[1024] = {0};
                          std::cout << "Listening for msg" << std::endl;
                          while (!stopListening)
                          {
                              int valread = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
                              if (valread < 0)
                              {
                                  std::cerr << "Receive failed." << std::endl;
                                  break;
                              }
                              else if (valread == 0)
                              {
                                  std::cout << "Connection closed by server." << std::endl;
                                  break;
                              }
                              else
                              {
                                  buffer[valread] = '\0';
                                  std::cout << "Received message from server: " << buffer << std::endl;
                                  outFile << buffer; // 将接收到的数据写入文件
                              }
                              memset(buffer, 0, sizeof(buffer));
                          }
                          outFile.close(); // 关闭文件
                          std::cout << "File closed successfully: server_response.txt" << std::endl;
                          closesocket(client_fd); });

        t.join();

#ifdef _WIN32
        WSACleanup();
#endif
        return 0;
    }

    void sendContent()
    {
        std::string msg = "GET / HTTP/1.1\r\nHost: " + domain + "\r\nConnection: Close\r\n\r\n";
        int result = send(client_fd, msg.c_str(), msg.length(), 0);
        if (result < 0)
        {
            std::cerr << "Send failed." << std::endl;
        }
        else
        {
            std::cout << "Successfully sent " << result << " bytes\n";
        }
    }
};

int main()
{
#ifdef _WIN32
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        std::cerr << "Failed to initialize Winsock." << std::endl;
        return -1;
    }
#endif

    HttpRequest httprequest("www.baidu.com", 80);
    httprequest.connect_to();

#ifdef _WIN32
    WSACleanup();
#endif

    return 0;
}

API函数

gethostbyname()

gethostbyname函数用于通过域名或主机名获取其对应的IP地址信息。它返回一个指向hostent结构体的指针,该结构体包含了主机的相关信息。

gethostbyname函数原型
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
hostent结构体内容

hostent结构体定义如下:

struct hostent {
    char *h_name;         // 官方主机名
    char **h_aliases;     // 主机别名列表
    int h_addrtype;       // 地址类型(如AF_INET表示IPv4)
    int h_length;         // 地址长度
    char **h_addr_list;   // 地址列表
};
  • h_name:官方主机名。例如,www.baidu.com的官方主机名可能是www.a.shifen.com
  • h_aliases:主机的别名列表。一个主机可以有多个别名,这些别名也用于访问该主机。
  • h_addrtype:地址类型,通常为AF_INET表示IPv4地址。
  • h_length:地址长度,IPv4地址长度为4字节。
  • h_addr_list:地址列表,包含了主机的所有IP地址。对于IPv4地址,可以使用inet_ntoa函数将其转换为点分十进制格式。
工作原理
  • gethostbyname首先检查本地的/etc/hosts文件,如果找到匹配的主机名,则返回相应的IP地址。
  • 如果未在hosts文件中找到匹配项,它会向DNS服务器发送查询请求,以获取主机名对应的IP地址。
注意事项
  • gethostbyname只能返回IPv4地址。如果需要获取IPv6地址或同时获取IPv4和IPv6地址,应使用getaddrinfo函数。
  • 返回的hostent结构体的内存由系统管理,因此在使用完毕后不需要手动释放。

gethostbyaddr()

在Linux系统中,将IP地址转换成域名(即反向DNS解析)可以使用gethostbyaddr函数。这个函数是POSIX标准的一部分,用于根据IP地址查找对应的主机名。以下是关于gethostbyaddr函数的详细说明和使用示例:

函数原型
#include <netdb.h>
struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
参数说明
  • addr:指向IP地址的指针。对于IPv4地址,应该是一个指向struct in_addr的指针;对于IPv6地址,应该是一个指向struct in6_addr的指针。
  • len:地址的长度。对于IPv4地址,长度为sizeof(struct in_addr);对于IPv6地址,长度为sizeof(struct in6_addr)
  • type:地址类型,可以是AF_INET(IPv4)或AF_INET6(IPv6)。
返回值
  • 成功时返回指向hostent结构体的指针,该结构体包含了主机的相关信息。
  • 失败时返回NULL,可以通过h_errno获取错误原因(例如使用herror函数)。
示例代码

以下是一个使用gethostbyaddr函数进行反向DNS解析的示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>

int main() {
    struct in_addr ip_addr;
    struct hostent *host;

    // 示例IP地址
    inet_pton(AF_INET, "8.8.8.8", &ip_addr);

    // 执行反向DNS解析
    host = gethostbyaddr(&ip_addr, sizeof(ip_addr), AF_INET);
    if (host == NULL) {
        herror("gethostbyaddr failed");
        return EXIT_FAILURE;
    }

    // 输出主机名
    printf("Hostname: %s\n", host->h_name);

    return EXIT_SUCCESS;
}
注意事项
  • 在使用gethostbyaddr之前,确保系统中已经配置了DNS服务器,并且DNS服务器能够响应反向查询请求。
  • gethostbyaddr函数可能不是线程安全的,如果需要在多线程环境中使用,可以考虑使用getaddrinfo函数的反向解析功能。

inet_ntoa()

  • 功能:将IPv4地址的网络字节序二进制形式转换为点分十进制的字符串表示形式。

  • 参数:

    • struct in_addr in:包含IPv4地址的in_addr结构体。
  • 返回值:

    • 返回指向转换后的字符串的指针。该字符串是静态分配的,因此每次调用inet_ntoa都会覆盖上一次的结果
  • 注意事项

    • inet_ntoa只支持IPv4地址,且返回的字符串是静态分配的,因此在多线程环境中使用时需要特别注意。
    • 对于IPv6地址或需要线程安全的场景,推荐使用inet_ptoninet_ntop
struct in_addr ip_addr;
inet_pton(AF_INET, "192.168.1.1", &ip_addr);
char *ip_str = inet_ntoa(ip_addr);
printf("IP address: %s\n", ip_str);

inet_ntop()

  • 功能:将IP地址的网络字节序二进制形式转换为字符串表示形式。
  • 参数:
    • int af:地址族,可以是AF_INET(IPv4)或AF_INET6(IPv6)。
    • const void *src:指向二进制IP地址的指针。
    • char *dst:指向用于存储转换后的字符串的缓冲区的指针。
    • socklen_t size:缓冲区的大小。
  • 返回值:
    • 成功时返回指向dst的指针。
    • 失败时返回NULL
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    struct in_addr ip_addr;
    inet_pton(AF_INET, "192.168.1.1", &ip_addr);

    char ip_str[INET_ADDRSTRLEN];
    const char *result = inet_ntop(AF_INET, &ip_addr, ip_str, INET_ADDRSTRLEN);
    if (result == NULL) {
        perror("inet_ntop");
        exit(EXIT_FAILURE);
    }
    printf("IP address in string format: %s\n", ip_str);
    return 0;
}

inet_pton()

  • 功能:将IP地址的字符串表示形式转换为网络字节序的二进制形式。
  • 参数:
    • int af:地址族,可以是AF_INET(IPv4)或AF_INET6(IPv6)。
    • const char *src:指向IP地址字符串的指针。
    • void *dst:指向存储转换后的二进制地址的缓冲区的指针。
  • 返回值:
    • 成功时返回1。
    • 输入字符串无效时返回0。
    • 出错时返回-1。
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    struct in_addr ip_addr;
    int result = inet_pton(AF_INET, "192.168.1.1", &ip_addr);
    if (result != 1) {
        perror("inet_pton");
        exit(EXIT_FAILURE);
    }
    printf("IP address in binary format: %u.%u.%u.%u\n",
           (unsigned char)ip_addr.s_addr,
           (unsigned char)(ip_addr.s_addr >> 8),
           (unsigned char)(ip_addr.s_addr >> 16),
           (unsigned char)(ip_addr.s_addr >> 24));
    return 0;
}