当前位置: 首页 > backend >正文

【C语言网络编程】HTTP 客户端请求(基于 Socket 的完整实现)

一、前言

在浏览器中,我们输入网址点击回车,就可以打开网页。那么这个过程中到底发生了什么?其实背后就是浏览器作为HTTP 客户端,向服务器发起了一个 HTTP 请求

本篇博客将手把手用纯 C 语言实现一个简洁版的“浏览器行为”:

  • 输入域名和资源路径

  • 使用 Socket 建立 TCP 连接

  • 构造并发送 HTTP GET 请求

  • 接收服务器响应内容(HTML 页面)

  • 打印到终端

核心代码不足百行,帮助你彻底搞懂 HTTP 请求的底层流程。

二、程序运行效果

以访问百度首页 / 为例:

./http_client www.baidu.com /

输出:

response : HTTP/1.1 200 OK
Date: ...
Content-Type: text/html
...<html>...</html>

三、功能结构流程图(文字版)

下面是整个程序的结构流程图

输入:主机名 和 资源路径│├──▶ 1. 解析主机名为 IP 地址(host_to_ip)│       └─ 调用 DNS,获取如 "14.215.177.39"│├──▶ 2. 创建 TCP Socket 并连接 80 端口(http_create_socket)│├──▶ 3. 构造 HTTP 请求报文(GET + Host + Connection)│├──▶ 4. 发送请求数据(send)│├──▶ 5. 等待并接收服务器响应(select + recv)│└──▶ 6. 拼接为字符串返回并输出

四、代码模块拆解

1. 解析主机名为 IP 地址

char *host_to_ip(const char *hostname) {struct hostent *host_entry = gethostbyname(hostname);if (host_entry) {return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);}return NULL;
}

作用:使用 DNS 把域名转换为 IPv4 地址,例如:www.baidu.com14.215.177.39

2. 创建并连接 Socket

int http_create_socket(char *ip) {int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in sin = {0};sin.sin_family = AF_INET;sin.sin_port = htons(80);sin.sin_addr.s_addr = inet_addr(ip);if (0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {return -1;}fcntl(sockfd, F_SETFL, O_NONBLOCK);return sockfd;
}

作用:基于目标 IP 地址创建 TCP socket,并连接到 80 端口(HTTP 服务端口)

3. 构造并发送 HTTP 请求

sprintf(buffer,"GET %s %s\r\n""Host: %s\r\n""%s\r\n""\r\n",resource, HTTP_VERSION,hostname,CONNETION_TYPE);send(sockfd, buffer, strlen(buffer), 0);

作用:拼接出符合标准的 GET 请求报文,并通过 socket 发送出去。

请求示例:

GET / HTTP/1.1Host: www.baidu.comConnection:close

4. 使用 select + recv 接收服务器响应

fd_set fdread;
FD_ZERO(&fdread);
FD_SET(sockfd, &fdread);
struct timeval tv = {5, 0};char *result = malloc(1);
result[0] = '\0';while (1) {int selection = select(sockfd + 1, &fdread, NULL, NULL, &tv);if (!selection || !FD_ISSET(sockfd, &fdread)) break;memset(buffer, 0, BUFFER_SIZE);int len = recv(sockfd, buffer, BUFFER_SIZE, 0);if (len == 0) break;result = realloc(result, strlen(result) + len + 1);strncat(result, buffer, len);
}

作用

  • 使用 select() 等待 socket 变为可读

  • 调用 recv() 读取响应内容

  • 使用动态字符串拼接响应文本

5. 主函数调用逻辑

int main(int argc, char *argv[]) {if (argc < 3) {printf("Usage: %s <hostname> <resource>\n", argv[0]);return -1;}char *response = http_send_request(argv[1], argv[2]);printf("response : %s\n", response);free(response);
}

作用:从命令行读取目标域名和资源路径,调用 HTTP 请求函数并打印响应结果。

五、完整代码

#include <stdio.h>      // 标准输入输出函数,如 printf
#include <string.h>     // 字符串函数,如 strlen, memset, strncat
#include <stdlib.h>     // 内存分配函数,如 malloc, realloc#include <sys/socket.h> // 套接字相关函数,如 socket, connect, send, recv
#include <netinet/in.h> // 地址结构 struct sockaddr_in
#include <arpa/inet.h>  // IP 地址转换函数,如 inet_addr, inet_ntoa
#include <unistd.h>     // close 函数等系统调用#include <netdb.h>      // gethostbyname 用于域名解析
#include <fcntl.h>      // fcntl 函数设置非阻塞// HTTP 协议版本
#define HTTP_VERSION        "HTTP/1.1"
// HTTP 请求头的连接类型(请求后关闭连接)
#define CONNETION_TYPE      "Connection:close\r\n"
// 缓冲区大小
#define BUFFER_SIZE         4096// 将主机名转换为 IP 地址
char *host_to_ip(const char *hostname){// 通过 DNS 获取主机条目(IP 信息等)struct hostent *host_entry = gethostbyname(hostname); // 将二进制 IP 地址转换为字符串形式返回if (host_entry){return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);}return NULL; // 解析失败返回 NULL
}// 创建 TCP socket 并连接指定 IP 的 80 端口
int http_create_socket(char *ip){// 创建一个 TCP socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址信息struct sockaddr_in sin = {0};sin.sin_family = AF_INET;             // 使用 IPv4sin.sin_port = htons(80);             // HTTP 默认端口(80),转换为网络字节序sin.sin_addr.s_addr = inet_addr(ip);  // 将字符串形式的 IP 转为网络字节序// 尝试连接服务器if (0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))){return -1; // 连接失败返回 -1}// 设置 socket 为非阻塞模式fcntl(sockfd, F_SETFL, O_NONBLOCK);return sockfd; // 返回 socket 文件描述符
}// 发送 HTTP 请求并接收响应内容
char *http_send_request(const char *hostname, const char *resource){// 将主机名解析为 IP 地址char *ip = host_to_ip(hostname);// 通过 IP 创建并连接 socketint sockfd = http_create_socket(ip);// 准备发送的 HTTP 请求报文char buffer[BUFFER_SIZE] = {0};sprintf(buffer,"GET %s %s\r\n""Host: %s\r\n""%s\r\n""\r\n",resource, HTTP_VERSION,hostname,CONNETION_TYPE);// 发送 HTTP 请求send(sockfd, buffer, strlen(buffer), 0);// 准备 select 用于等待 socket 可读fd_set fdread;FD_ZERO(&fdread);FD_SET(sockfd, &fdread);// 设置超时时间为 5 秒struct timeval tv;tv.tv_sec = 5;tv.tv_usec = 0;// 结果缓存,初始分配 4 个字节(但应该至少分配 1)char *result = malloc(sizeof(int));memset(result, 0, sizeof(int));  // 清空内存// 不断读取 socket 返回的数据while (1) {// 使用 select 等待可读事件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) {break;}// 重新分配 result 空间,并拼接新接收到的数据result = realloc(result, (strlen(result) + len + 1) * sizeof(char));strncat(result, buffer, len);}}return result; // 返回完整的响应字符串
}// 主函数,命令行调用格式:./程序名 www.xxx.com /path
int main(int argc, char *argv[]){// 参数不足,提示错误if (argc < 3) return -1;// 调用 HTTP 请求函数char *response = http_send_request(argv[1], argv[2]);// 打印响应内容printf("response : %s\n", response);// 释放申请的内存free(response);
}

https://github.com/0voice

http://www.xdnf.cn/news/16371.html

相关文章:

  • 神经网络知识讨论
  • 网易大模型算法岗面经80道
  • 【学习笔记】MimicGen: 基于人类演示的可扩展机器人学习数据生成系统
  • 批量重命名带编号工具,附免费地址
  • idea打开后project窗口未显示项目名称的解决方案
  • k8s的权限
  • tlias智能学习辅助系统--Filter(过滤器)
  • Ansible列出常见操作系统的发行版,Ansible中使用facts变量的两种方式
  • CH341 Linux驱动 没有 /dev/ttyCH341USB0
  • Linux文件系统管理——NFS服务端的安装配置与NFS客户端的安装与挂载实操教程
  • 【AI】联网模式
  • Scrapy分布式爬虫数据统计全栈方案:构建企业级监控分析系统
  • GPU运维常见问题处理
  • 【C++】stack和queue的模拟实现
  • Java基础day17-LinkedHashMap类,TreeMap类和集合工具类
  • 基于POD和DMD方法的压气机叶片瞬态流场分析与神经网络预测
  • 基于遗传算法的多无人车协同侦察与安全保护策略优化
  • CUDA杂记--FP16与FP32用途
  • Redis面试精讲 Day 5:Redis内存管理与过期策略
  • 汇编语言中的通用寄存器及其在逆向工程中的应用
  • 计划任务(at和cron命令介绍及操作)
  • MySQL事务原理
  • 应用程序 I/O 接口
  • 【MySQL 数据库】MySQL基本查询(第二节)
  • 系统性学习C语言-第二十三讲-文件操作
  • 谷歌无法安装扩展程序解决方法(也许成功)
  • Kubernetes 与 Docker的爱恨情仇
  • STM32-定时器的基本定时/计数功能实现配置教程(寄存器版)
  • 【工具】好用的浏览器AI助手
  • 用unity开发教学辅助软件---幼儿绘本英语拼读