http客户端请求:c++实现

1.了解http请求的流程

  1. 先建立tcp连接
  2. 在tcp连接socket基础上,发送http的协议请求
  3. 服务器在tcp连接socket基础上返回一个http协议的response

2.编程框架

  1. www.baidu.com-->翻译为ip地址。DNS
  2. tcp连接这个ip地址:端口
  3. 发送http协议

2.1引入头文件、定义

// 引入标准输入输出库,用于printf等函数
#include <stdio.h>
// 引入字符串处理库,用于strlen、strncat等函数
#include <string.h>
// 引入标准库,用于malloc、realloc、free等内存管理函数
#include <stdlib.h>

// 引入socket相关函数库,包含socket、connect等函数定义
#include <sys/socket.h>
// 引入网络地址结构定义,包含sockaddr_in等
#include <netinet/in.h>
// 引入IP地址转换函数,包含inet_addr、inet_ntoa等
#include <arpa/inet.h>
// 引入unix标准函数库,包含close、read等函数
#include <unistd.h>

// 引入主机名解析库,包含gethostbyname等函数
#include <netdb.h>
// 引入文件控制函数库,包含fcntl函数(用于设置非阻塞模式)
#include <fcntl.h>
// 定义HTTP协议版本宏
#define HTTP_VERSION		"HTTP/1.1"
// 定义连接类型宏(关闭连接)
#define CONNETION_TYPE		"Connection: close\r\n" 
// 定义缓冲区大小宏
#define BUFFER_SIZE		4096

2.2将主机名解析为IP地址   例如:www.baidu.com --> 对应的IP地址

char *host_to_ip(const char *hostname) {

	// 调用gethostbyname进行DNS解析,获取主机信息结构体
	struct hostent *host_entry = gethostbyname(hostname); // 执行DNS解析

	// 注释说明:inet_ntoa函数功能是将32位无符号整数转换为点分十进制IP字符串
	// 例如:0x12121212 --> "18.18.18.18"
	if (host_entry) {     
		// 从主机信息中提取第一个IP地址并转换为字符串返回
		// h_addr_list是IP地址列表,取第一个地址并转换为in_addr结构指针
		return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
	} 

	// 解析失败返回NULL
	return NULL;
} 

2.3创建HTTP连接的socket并连接到目标IP

 fcntl(sockfd, F_SETFL, O_NONBLOCK);这步 fcntl 是为 HTTP 客户端(TCP 连接)规避 “阻塞卡死” 问题 —— 把套接字改成非阻塞后,connect/send/recv 不会无限等待,后续可以通过 “超时控制 + 重试” 实现灵活、可靠的 HTTP 通信。

int http_create_socket(char *ip) {

	// 创建TCP socket(AF_INET:IPv4协议,SOCK_STREAM:流式套接字,0:默认协议)
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

	// 初始化socket地址结构,清0
	struct sockaddr_in sin = {0};
	sin.sin_family = AF_INET;          // 设置地址族为IPv4
	sin.sin_port = htons(80);          // 设置端口为80(HTTP默认端口),htons将主机字节序转为网络字节序
 	sin.sin_addr.s_addr = inet_addr(ip); // 将IP字符串转换为网络字节序的整数

	// 连接到目标服务器,如果连接失败返回-1
	if (0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
		return -1;
	}
	fcntl(sockfd, F_SETFL, O_NONBLOCK);

	// 返回创建成功的socket描述符
	return sockfd;

}

2.4发送HTTP GET请求并获取响应

resourece:/ 是 HTTP 的 “默认访问路径”,作用是 “请求服务器的首页(或根路径默认资源)”,不用写具体文件名,服务器会自动匹配预设的默认内容。

fd_set fdread;:创建一个 “事件监听清单”

FD_ZERO(&fdread);:清空清单,避免垃圾值

FD_SET(sockfd, &fdread);:把目标 socket 加入监听清单

select(sockfd + 1, &fdread, NULL, NULL, &timeout);只监听可读事件

sockfd + 1:由于select底层用一个 “二进制位集合”(位掩码)来跟踪哪些 fd 有事件(比如可读、可写),fd 是从 0 开始的,第 N 位对应 fd=N-1

  • 第 0 位(二进制 0001)对应 fd=0(标准输入)

  • 第 1 位(二进制 0010)对应 fd=1(标准输出)

  • 第 2 位(二进制 0100)对应 fd=2(标准错误

  • 要监听的最大 fd 就是 sockfd(比如 3);

  • 所以需要告诉 select:“检查到第 3 位为止”,也就是 3 + 1 = 4

  • 如果你直接传 sockfd(3),select 会误以为 “只检查到第 2 位”,就会漏掉 sockfd=3 的事件,导致监听失效。

char * http_send_request(const char *hostname, const char *resource) {

	// 将主机名解析为IP地址
	char *ip = host_to_ip(hostname); 
	// 创建socket并连接到目标IP
	int sockfd = http_create_socket(ip);

	// 定义缓冲区用于构建HTTP请求
	char buffer[BUFFER_SIZE] = {0};
	// 格式化构建HTTP GET请求报文
	sprintf(buffer, 
"GET %s %s\r\n\
Host: %s\r\n\
%s\r\n\
\r\n",
	resource, HTTP_VERSION,  // 请求资源路径和HTTP版本
	hostname,                // Host头字段(指定主机名)
	CONNETION_TYPE           // 连接类型(关闭连接)
	);

	// 发送HTTP请求到服务器
	send(sockfd, buffer, strlen(buffer), 0);

	// 使用select实现I/O多路复用,等待socket可读
	fd_set fdread;  // 文件描述符集合
	
	FD_ZERO(&fdread);          // 清空文件描述符集合
	FD_SET(sockfd, &fdread);   // 将socket描述符加入集合

	// 设置select超时时间
	struct timeval tv;
	tv.tv_sec = 5;    // 秒
	tv.tv_usec = 0;   // 微秒

	// 分配内存存储响应结果(初始分配int大小的空间)
	char *result = malloc(sizeof(int));
	// 初始化内存为0
	memset(result, 0, sizeof(int));
	
	// 循环读取服务器响应
	while (1) {

		// 调用select等待socket可读,超时返回
		// 参数:最大文件描述符+1,读集合,写集合,异常集合,超时时间
		int selection = select(sockfd+1, &fdread, NULL, NULL, &tv);
		// 如果超时或socket不可读,则退出循环
		if (!selection || !FD_ISSET(sockfd, &fdread)) {
			break;
		} else {

			// 清空缓冲区
			memset(buffer, 0, BUFFER_SIZE);
			// 从socket读取数据
			int len = recv(sockfd, buffer, BUFFER_SIZE, 0);
			if (len == 0) { // 如果读取长度为0,表示连接已关闭
				break;
			}
			// 重新分配内存以容纳新读取的数据
			// 新大小 = 现有长度 + 新读取长度 + 1(字符串结束符)
			result = realloc(result, (strlen(result) + len + 1) * sizeof(char));
			// 将新读取的数据追加到结果中
			strncat(result, buffer, len);
		}
	}
	// 返回完整的响应数据
	return result;
}

2.5主函数

int main(int argc, char *argv[]) {

	// 检查命令行参数是否足够(需要主机名和资源路径)
	if (argc < 3) return -1;

	// 发送HTTP请求并获取响应(argv[1]是主机名,argv[2]是资源路径)
	char *response = http_send_request(argv[1], argv[2]);
	// 打印响应结果
	printf("response : %s\n", response);

	// 释放动态分配的内存
	free(response);
	
	// 程序正常退出
	return 0;  
}

2.6结果测试

输入./httprequest www.0voice.com /   与服务器网页首页一致,http请求成功

https://github.com/0voice

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值