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_pton
和inet_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;
}