HTTP 中间件全链路深度分析
·
第一部分 基础分析
/** * @file http_middleware_analysis.c * @brief HTTP 中间件从请求到响应的完整链路分析 * * 分析范围: * 1. HTTP 协议栈架构(OSI 七层模型视角) * 2. 开源 HTTP 中间件源码树形分析(Nginx/Envoy/HAProxy) * 3. 请求处理全链路追踪 * 4. 内存管理/缓冲区/连接池 * 5. 事件驱动模型(Reactor/Proactor) * 6. 性能分析与调优 * 7. 常见问题定位与调试 */
一、HTTP 中间件架构全景
1.1 OSI 七层模型视角
【HTTP 中间件在协议栈中的位置】 ┌─────────────────────────────────────────────────────────────────┐ │ 应用层 (Application Layer) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ HTTP/HTTPS/HTTP2/HTTP3 │ │ │ │ (请求解析、路由、业务逻辑、响应生成) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ ├─────────────────────────────────────────────────────────────────┤ │ 表示层 (Presentation Layer) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ TLS/SSL (加密/解密、证书验证) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ ├─────────────────────────────────────────────────────────────────┤ │ 会话层 (Session Layer) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Session/Cookie 管理、Keep-Alive │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ ├─────────────────────────────────────────────────────────────────┤ │ 传输层 (Transport Layer) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ TCP/UDP (连接管理) │ │ │ │ (Nginx/Envoy/HAProxy 核心处理层) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ ├─────────────────────────────────────────────────────────────────┤ │ 网络层 (Network Layer) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ IP (路由) │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ ├─────────────────────────────────────────────────────────────────┤ │ 数据链路层 (Data Link Layer) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Ethernet │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ ├─────────────────────────────────────────────────────────────────┤ │ 物理层 (Physical Layer) │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 网卡/光纤 │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘
二、开源 HTTP 中间件源码树形分析
2.1 Nginx 源码目录树
Nginx 源码结构 (nginx-1.24.0) ├── auto/ # 自动编译脚本 │ ├── cc/ # 编译器检测 │ ├── feature/ # 功能检测 │ ├── lib/ # 库检测 │ └── options # 编译选项解析 │ ├── src/ # 核心源码 ★ │ ├── core/ # 核心基础模块 │ │ ├── nginx.c # 主入口函数 (main) ★ │ │ ├── ngx_cycle.c # 主循环/生命周期管理 ★ │ │ ├── ngx_connection.c # 连接管理 ★ │ │ ├── ngx_buf.c # 缓冲区管理 ★ │ │ ├── ngx_array.c # 动态数组 │ │ ├── ngx_list.c # 链表 │ │ ├── ngx_queue.c # 队列 │ │ ├── ngx_hash.c # 哈希表 │ │ ├── ngx_string.c # 字符串处理 │ │ ├── ngx_file.c # 文件操作 │ │ ├── ngx_log.c # 日志系统 │ │ ├── ngx_palloc.c # 内存池管理 ★ │ │ ├── ngx_radix_tree.c # 基数树 │ │ ├── ngx_open_file_cache.c # 文件缓存 │ │ └── ngx_rbtree.c # 红黑树 │ │ │ ├── event/ # 事件处理模块 ★ │ │ ├── ngx_event.c # 事件核心 ★ │ │ ├── ngx_event_accept.c # 接受连接 │ │ ├── ngx_event_connect.c # 连接后端 │ │ ├── ngx_event_pipe.c # 事件管道 │ │ ├── ngx_event_timer.c # 定时器管理 │ │ ├── ngx_event_posted.c # 发布事件 │ │ ├── modules/ # 事件模块 │ │ │ ├── ngx_epoll_module.c # epoll (Linux) │ │ │ ├── ngx_kqueue_module.c # kqueue (BSD/macOS) │ │ │ ├── ngx_iocp_module.c # IOCP (Windows) │ │ │ ├── ngx_select_module.c # select │ │ │ └── ngx_poll_module.c # poll │ │ └── ... │ │ │ ├── http/ # HTTP 模块 ★ │ │ ├── ngx_http.c # HTTP 核心初始化 │ │ ├── ngx_http_request.c # HTTP 请求处理 ★ │ │ ├── ngx_http_parse.c # HTTP 请求解析 ★ │ │ ├── ngx_http_header_filter.c # 响应头过滤 │ │ ├── ngx_http_write_filter.c # 响应写过滤 │ │ ├── ngx_http_core_module.c # HTTP 核心模块 │ │ ├── ngx_http_variables.c # 变量处理 │ │ ├── ngx_http_upstream.c # 上游/代理 ★ │ │ ├── ngx_http_upstream_round_robin.c # 负载均衡 │ │ ├── modules/ # HTTP 模块 │ │ │ ├── ngx_http_proxy_module.c # 代理模块 │ │ │ ├── ngx_http_fastcgi_module.c # FastCGI │ │ │ ├── ngx_http_uwsgi_module.c # uWSGI │ │ │ ├── ngx_http_scgi_module.c # SCGI │ │ │ ├── ngx_http_memcached_module.c # Memcached │ │ │ ├── ngx_http_limit_conn_module.c # 连接限制 │ │ │ ├── ngx_http_limit_req_module.c # 请求限制 │ │ │ ├── ngx_http_rewrite_module.c # 重写 │ │ │ ├── ngx_http_ssl_module.c # SSL/TLS │ │ │ ├── ngx_http_gzip_module.c # 压缩 │ │ │ └── ngx_http_static_module.c # 静态文件 │ │ └── ... │ │ │ ├── stream/ # 流模块 (TCP/UDP代理) │ │ ├── ngx_stream.c │ │ ├── ngx_stream_core_module.c │ │ ├── ngx_stream_proxy_module.c │ │ └── modules/ │ │ │ ├── mail/ # 邮件模块 (POP3/IMAP/SMTP) │ │ ├── ngx_mail.c │ │ └── modules/ │ │ │ ├── os/ # 操作系统相关 │ │ ├── unix/ # Unix/Linux 实现 │ │ │ ├── ngx_os.h │ │ │ ├── ngx_linux.h │ │ │ ├── ngx_posix_init.c │ │ │ ├── ngx_process.c │ │ │ ├── ngx_process_cycle.c # 进程管理 ★ │ │ │ └── ngx_socket.c │ │ └── win32/ # Windows 实现 │ │ │ └── misc/ # 杂项 │ └── ngx_cpp_test_module.cpp │ ├── contrib/ # 贡献脚本 │ ├── geo2nginx.pl │ ├── unicode2nginx/ │ └── vim/ │ ├── conf/ # 默认配置示例 │ ├── nginx.conf # 主配置文件 │ ├── mime.types # MIME 类型映射 │ ├── fastcgi_params │ ├── proxy_params │ ├── uwsgi_params │ └── scgi_params │ ├── html/ # 默认网页 │ ├── index.html │ └── 50x.html │ ├── man/ # 帮助手册 │ └── nginx.8 │ └── configure # 编译配置脚本
2.2 Nginx 核心模块调用树
【Nginx 主流程函数调用树】 main() [nginx.c] ├── ngx_strerror_init() # 初始化错误字符串 ├── ngx_get_options() # 解析命令行参数 ├── ngx_time_init() # 初始化时间 ├── ngx_log_init() # 初始化日志 ├── ngx_memalign() # 内存对齐分配 ├── ngx_init_cycle() # 初始化主循环 ★ │ ├── ngx_timezone_update() # 时区更新 │ ├── ngx_conf_parse() # 解析配置文件 ★ │ │ └── ngx_conf_handler() # 处理配置指令 │ ├── ngx_create_pool() # 创建内存池 │ ├── ngx_open_listening_sockets() # 打开监听socket │ ├── ngx_event_core_init() # 事件核心初始化 │ ├── ngx_event_process_init() # 事件处理初始化 │ └── ngx_http_init() # HTTP模块初始化 │ ├── ngx_single_process_cycle() / ngx_master_process_cycle() │ ├── ngx_start_worker_processes() # 启动worker进程 │ │ └── ngx_spawn_process() # 创建子进程 │ │ └── ngx_worker_process_cycle() # worker主循环 │ │ ├── ngx_process_events_and_timers() # 事件循环 ★ │ │ │ ├── ngx_event_find_timer() # 查找定时器 │ │ │ ├── ngx_process_events() # 处理事件(epoll_wait) │ │ │ │ └── ngx_epoll_process_events() # epoll实现 │ │ │ │ ├── epoll_wait() # 等待事件 │ │ │ │ └── ngx_event_accept() # 接受连接 │ │ │ │ └── ngx_http_init_connection() │ │ │ │ └── ngx_http_process_request_line() │ │ │ │ ├── ngx_http_parse_request_line() # 解析请求行 │ │ │ │ └── ngx_http_process_request_headers() # 解析头部 │ │ │ │ └── ngx_http_process_request() │ │ │ │ ├── ngx_http_handler() # 业务处理 │ │ │ │ │ ├── ngx_http_core_run_phases() # 阶段处理 │ │ │ │ │ │ ├── NGX_HTTP_POST_READ_PHASE │ │ │ │ │ │ ├── NGX_HTTP_SERVER_REWRITE_PHASE │ │ │ │ │ │ ├── NGX_HTTP_FIND_CONFIG_PHASE │ │ │ │ │ │ ├── NGX_HTTP_REWRITE_PHASE │ │ │ │ │ │ ├── NGX_HTTP_POST_REWRITE_PHASE │ │ │ │ │ │ ├── NGX_HTTP_PREACCESS_PHASE │ │ │ │ │ │ ├── NGX_HTTP_ACCESS_PHASE │ │ │ │ │ │ ├── NGX_HTTP_POST_ACCESS_PHASE │ │ │ │ │ │ ├── NGX_HTTP_TRY_FILES_PHASE │ │ │ │ │ │ ├── NGX_HTTP_CONTENT_PHASE # ★ 内容生成 │ │ │ │ │ │ │ ├── ngx_http_proxy_handler() # 代理 │ │ │ │ │ │ │ ├── ngx_http_fastcgi_handler() # FastCGI │ │ │ │ │ │ │ ├── ngx_http_static_handler() # 静态文件 │ │ │ │ │ │ │ └── ngx_http_index_handler() # 索引 │ │ │ │ │ │ └── NGX_HTTP_LOG_PHASE │ │ │ │ └── ngx_http_output_filter() # 输出过滤 │ │ │ │ ├── ngx_http_header_filter() # 头部过滤 │ │ │ │ ├── ngx_http_write_filter() # 写过滤 │ │ │ │ └── ngx_http_send_special() # 发送响应 │ │ │ └── ngx_event_expire_timers() # 过期定时器 │ │ ├── ngx_quit = 1? # 检查退出标志 │ │ └── ngx_reopen = 1? # 检查日志重开 │ │ │ └── ngx_signal_handler() # 信号处理 │ └── ngx_master_process_exit() # 退出清理
2.3 Envoy 源码目录树
Envoy 源码结构 (envoy-1.28.0) ├── bazel/ # Bazel 编译配置 ├── ci/ # CI/CD 配置 ├── configs/ # 示例配置 ├── docs/ # 文档 ├── generated/ # 生成代码 ├── include/ # 公共头文件 ★ │ └── envoy/ │ ├── api/ # API 定义 │ ├── common/ # 公共类型 │ ├── config/ # 配置 │ ├── event/ # 事件/调度 ★ │ │ ├── dispatcher.h # 事件分发器 │ │ ├── file_event.h # 文件事件 │ │ ├── timer.h # 定时器 │ │ └── signal.h # 信号 │ ├── http/ # HTTP 处理 ★ │ │ ├── codec.h # HTTP 编解码器 │ │ ├── filter.h # 过滤器接口 │ │ ├── header_map.h # 头部映射 │ │ └── router.h # 路由 │ ├── network/ # 网络层 │ │ ├── connection.h # 连接 │ │ ├── listener.h # 监听器 │ │ ├── filter.h # 网络过滤器 │ │ └── socket.h # Socket │ ├── server/ # 服务器核心 │ │ ├── configuration.h # 配置 │ │ ├── instance.h # 实例 │ │ ├── options.h # 启动选项 │ │ └── worker.h # 工作线程 │ ├── upstream/ # 上游/负载均衡 │ │ ├── cluster_manager.h # 集群管理 │ │ ├── load_balancer.h # 负载均衡器 │ │ ├── health_checker.h # 健康检查 │ │ └── host.h # 主机 │ ├── stats/ # 统计 │ │ ├── stats.h │ │ └── scope.h │ ├── access_log/ # 访问日志 │ ├── admin/ # 管理接口 │ ├── extensions/ # 扩展 │ └── thread/ # 线程 │ └── thread.h │ ├── source/ # 源文件 ★ │ ├── common/ # 公共实现 │ │ ├── event/ # 事件实现 │ │ │ ├── dispatcher_impl.cc # 调度器实现 │ │ │ ├── file_event_impl.cc │ │ │ ├── timer_impl.cc │ │ │ └── signal_impl.cc │ │ ├── http/ # HTTP 实现 │ │ │ ├── codec_impl.cc # HTTP/1.1/2 编解码 │ │ │ ├── header_map_impl.cc # 头部管理 │ │ │ ├── filter_manager.cc # 过滤器管理 │ │ │ └── router.cc # 路由逻辑 │ │ ├── network/ # 网络实现 │ │ │ ├── connection_impl.cc # 连接实现 │ │ │ ├── listener_impl.cc # 监听器实现 │ │ │ └── socket_impl.cc # Socket实现 │ │ ├── upstream/ # 上游实现 │ │ │ ├── cluster_manager_impl.cc │ │ │ ├── load_balancer_impl.cc │ │ │ └── health_checker_impl.cc │ │ └── ... │ │ │ ├── server/ # 服务器实现 │ │ ├── server.cc # 主入口 ★ │ │ ├── config_validation.cc │ │ ├── configuration_impl.cc │ │ ├── instance_impl.cc │ │ ├── options_impl.cc │ │ ├── worker_impl.cc # 工作线程实现 ★ │ │ └── guarddog_impl.cc # 守护 │ │ │ ├── extensions/ # 扩展模块 │ │ ├── filters/ # 过滤器 │ │ │ ├── http/ # HTTP过滤器 │ │ │ │ ├── router/ │ │ │ │ ├── cors/ │ │ │ │ ├── gzip/ │ │ │ │ ├── jwt_authn/ │ │ │ │ └── ... │ │ │ └── network/ # 网络过滤器 │ │ │ ├── tcp_proxy/ │ │ │ └── ... │ │ ├── access_loggers/ # 访问日志器 │ │ ├── stat_sinks/ # 统计输出 │ │ ├── transport_sockets/ # 传输层 │ │ │ ├── tls/ │ │ │ └── raw_buffer/ │ │ └── ... │ │ │ └── exe/ # 可执行入口 │ ├── main.cc # 主函数 ★ │ ├── main_common.cc │ └── startup.cc │ ├── test/ # 测试 │ ├── integration/ # 集成测试 │ ├── unit/ # 单元测试 │ ├── common/ # 公共测试 │ └── ... │ └── tools/ # 工具 ├── code_format/ ├── coverage/ └── ...
三、请求处理全链路追踪
3.1 HTTP 请求处理时序图
【Nginx HTTP 请求处理完整时序图】 Client Master Worker Epoll HTTP Core Upstream Backend │ │ │ │ │ │ │ │ TCP SYN │ │ │ │ │ │ ├──────────────>│ │ │ │ │ │ │ │ accept() │ │ │ │ │ │ ├──────────────>│ │ │ │ │ │ │ │ epoll_ctl() │ │ │ │ │ │ ├──────────────>│ │ │ │ │ │ │ │ │ │ │ │ HTTP GET │ │ epoll_wait() │ │ │ │ ├──────────────>│ │<──────────────┤ │ │ │ │ │ │ 返回可读事件 │ │ │ │ │ │ │ │ │ │ │ │ │ │ recv() │ │ │ │ │ │ ├──────────────>│ │ │ │ │ │ │ 接收请求数据 │ │ │ │ │ │ │ │ │ │ │ │ │ │ parse_request_line() │ │ │ │ │ │─────────────────────────────> │ │ │ │ │ │ 解析请求行 │ │ │ │ │ │<───────────────────────────── │ │ │ │ │ │ │ │ │ │ │ │ │ parse_request_headers() │ │ │ │ │ │─────────────────────────────> │ │ │ │ │ │ 解析请求头 │ │ │ │ │ │<───────────────────────────── │ │ │ │ │ │ │ │ │ │ │ │ │ find_location() │ │ │ │ │ │─────────────────────────────> │ │ │ │ │ │ │ │ │ │ │ │ │ run_phases() │ │ │ │ │ │ │─────────────────────────────> │ │ │ │ │ │ │ │ │ │ │ │ │ │ 匹配 proxy_pass │ │ │ │ │ │ │────────────────>│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ create_upstream() │ │ │ │ │──────────────>│ │ │ │ │ │ │ │ │ │ │ │ │ │ connect() │ │ │ │ │ │ │──────────────>│ │ │ │ │ │ │ │ TCP SYN │ │ │ │ │ │ ├──────────────>│ │ │ │ │ │ │ │ │ │ │ │ │ send request │ │ │ │ │ │ │──────────────>│──────────────>│ │ │ │ │ │ │ │ │ │ │ │ │ │ recv response│ │ │ │ │ │ │<──────────────│ │ │ │ │ │ recv response │ │ │ │ │ │ │<──────────────│ │ │ │ │ │ │ │ │ │ │ │ output_filter() │ │ │ │ │ │<───────────────────────────── │ │ │ │ │ │ │ │ │ │ │ │ │ send() │ │ │ │ │ │ ├──────────────>│ │ │ │ │ │ │ │ │ │ │ │ HTTP Response│ │ │ │ │ │ │<──────────────│ │ │ │ │ │ │ │ │ │ │ │ │ │ FIN │ │ │ │ │ │ ├──────────────>│ │ │ │ │ │ │ │ │ close() │ │ │ │ │ │ ├──────────────>│ │ │ │
3.2 请求生命周期阶段图
【Nginx HTTP 请求处理阶段 (Phase)】 ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_POST_READ_PHASE │ │ (读取请求体后,内容处理前) │ │ 典型模块: realip │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_SERVER_REWRITE_PHASE │ │ (server块重写规则) │ │ 典型模块: rewrite (server级) │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_FIND_CONFIG_PHASE │ │ (查找匹配的location) │ │ 核心阶段,不挂载模块 │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_REWRITE_PHASE │ │ (location块重写规则) │ │ 典型模块: rewrite (location级) │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_POST_REWRITE_PHASE │ │ (重写后处理) │ │ 内部跳转处理,不挂载模块 │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_PREACCESS_PHASE │ │ (访问前检查) │ │ 典型模块: limit_conn, limit_req │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_ACCESS_PHASE │ │ (访问控制) │ │ 典型模块: access, auth_basic, auth_request │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_POST_ACCESS_PHASE │ │ (访问后处理) │ │ 典型模块: satisfied (satisfy any) │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_TRY_FILES_PHASE │ │ (try_files处理) │ │ 核心阶段,不挂载模块 │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_CONTENT_PHASE │ │ (内容生成阶段) ★核心 │ │ 典型模块: proxy, fastcgi, static, index │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ NGX_HTTP_LOG_PHASE │ │ (日志记录) │ │ 典型模块: access_log │ └─────────────────────────────────────────────────────────────────┘
四、内存管理与缓冲区
4.1 Nginx 内存池架构
/**
* Nginx 内存池 (ngx_pool_t) 结构
* 源码位置: src/core/ngx_palloc.c
*/
/* 内存池数据结构 */
typedef struct ngx_pool_s {
ngx_pool_data_t d; /* 数据块信息 */
size_t max; /* 最大分配大小 */
ngx_pool_t *current; /* 当前使用的内存池 */
ngx_chain_t *chain; /* 缓冲区链 */
ngx_pool_large_t *large; /* 大块内存链表 */
ngx_pool_cleanup_t *cleanup; /* 清理回调 */
ngx_log_t *log; /* 日志 */
} ngx_pool_t;
/* 内存池数据块 */
typedef struct {
u_char *last; /* 当前可用位置 */
u_char *end; /* 块结束位置 */
ngx_pool_t *next; /* 下一个块 */
ngx_uint_t failed; /* 分配失败次数 */
} ngx_pool_data_t;
/* 大块内存节点 */
typedef struct ngx_pool_large_s {
ngx_pool_large_t *next; /* 下一个大块 */
void *alloc; /* 分配的内存指针 */
} ngx_pool_large_t;
/* 内存池操作流程 */
┌─────────────────────────────────────────────────────────────────┐
│ ngx_create_pool(size) │
│ 分配一个固定大小的内存池 (默认 16KB) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ ngx_palloc(pool, size) │
│ 分配内存,如果 size > pool->max (4KB),走大块分配 │
└─────────────────────────────────────────────────────────────────┘
↓ ↓
┌──────────────────────┐ ┌──────────────────────────────────┐
│ 小块分配 (<4KB) │ │ 大块分配 (≥4KB) │
│ 从 current 块取内存 │ │ 创建 large 节点,独立分配内存 │
│ 不足则创建新块 │ │ 链表管理,不释放直到池销毁 │
└──────────────────────┘ └──────────────────────────────────┘
↓ ↓
┌─────────────────────────────────────────────────────────────────┐
│ ngx_destroy_pool(pool) │
│ 释放所有块、大块内存,调用 cleanup 回调 │
└─────────────────────────────────────────────────────────────────┘
4.2 Nginx 缓冲区结构
/**
* Nginx 缓冲区 (ngx_buf_t) 结构
* 源码位置: src/core/ngx_buf.h
*/
typedef struct ngx_buf_s {
u_char *pos; /* 数据起始位置 */
u_char *last; /* 数据结束位置 */
off_t file_pos; /* 文件起始位置 */
off_t file_last; /* 文件结束位置 */
u_char *start; /* 缓冲区起始地址 */
u_char *end; /* 缓冲区结束地址 */
ngx_buf_tag_t tag; /* 缓冲区标签 */
ngx_file_t *file; /* 关联的文件 */
/* 标志位 */
unsigned temporary:1; /* 临时内存 */
unsigned memory:1; /* 内存映射 */
unsigned mmap:1; /* mmap映射 */
unsigned recycled:1; /* 可回收 */
unsigned in_file:1; /* 文件数据 */
unsigned flush:1; /* 刷新标记 */
unsigned sync:1; /* 同步标记 */
unsigned last_buf:1; /* 最后一个缓冲区 */
unsigned last_in_chain:1; /* 链中最后一个 */
unsigned shadow:1; /* 影子缓冲区 */
unsigned temp_file:1; /* 临时文件 */
} ngx_buf_t;
/* 缓冲区链 (ngx_chain_t) */
struct ngx_chain_s {
ngx_buf_t *buf; /* 缓冲区 */
ngx_chain_t *next; /* 下一个节点 */
};
4.3 Nginx 缓冲区池
/**
* 缓冲区池管理 - 避免频繁分配/释放
*/
/* 缓冲区池结构 */
typedef struct {
ngx_chain_t *free; /* 空闲链表 */
ngx_chain_t *busy; /* 使用中链表 */
ngx_chain_t *out; /* 输出链表 */
} ngx_chain_pool_t;
/* 获取缓冲区链 */
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool)
{
ngx_chain_t *cl;
/* 从空闲链表取 */
cl = pool->chain->free;
if (cl) {
pool->chain->free = cl->next;
return cl;
}
/* 空闲不足,新分配 */
cl = ngx_palloc(pool, sizeof(ngx_chain_t));
return cl;
}
/* 释放缓冲区链 */
void ngx_free_chain(ngx_pool_t *pool, ngx_chain_t *cl)
{
/* 放回空闲链表 */
cl->next = pool->chain->free;
pool->chain->free = cl;
}
五、事件驱动模型 (Reactor/Proactor)
5.1 Nginx 事件模型架构
/**
* Nginx 事件模型 (基于 epoll 实现)
* 源码位置: src/event/ngx_event.c
*/
/* 事件模块接口 */
typedef struct {
int (*add)(ngx_event_t *ev, int event, u_int flags);
int (*del)(ngx_event_t *ev, int event, u_int flags);
int (*enable)(ngx_event_t *ev, int event, u_int flags);
int (*disable)(ngx_event_t *ev, int event, u_int flags);
int (*add_conn)(ngx_connection_t *c);
int (*del_conn)(ngx_connection_t *c, u_int flags);
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags);
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
/* epoll 实现 */
static ngx_event_actions_t ngx_epoll_module_ctx = {
.add = ngx_epoll_add_event,
.del = ngx_epoll_del_event,
.enable = ngx_epoll_enable_event,
.disable = ngx_epoll_disable_event,
.add_conn = ngx_epoll_add_connection,
.del_conn = ngx_epoll_del_connection,
.process_events = ngx_epoll_process_events,
.init = ngx_epoll_init,
.done = ngx_epoll_done,
};
/* 事件结构体 */
struct ngx_event_s {
void *data; /* 关联的连接数据 */
unsigned write:1; /* 写事件标志 */
unsigned accept:1; /* 接受连接标志 */
/* 回调函数 */
void (*handler)(ngx_event_t *ev);
/* 定时器 */
ngx_rbtree_node_t timer;
/* 事件标志 */
unsigned active:1; /* 事件已注册 */
unsigned ready:1; /* 事件就绪 */
unsigned timedout:1; /* 已超时 */
unsigned error:1; /* 错误 */
unsigned canceled:1; /* 已取消 */
};
/* 事件循环核心 */
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
/* 计算定时器超时时间 */
timer = ngx_event_find_timer();
/* 处理事件 */
(void) ngx_process_events(cycle, timer, flags);
/* 处理定时器事件 */
ngx_event_expire_timers();
/* 处理posted事件 */
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
ngx_event_process_posted(cycle, &ngx_posted_events);
}
5.2 epoll 事件循环流程图
【Nginx epoll 事件循环】 ┌─────────────────────────────────────┐ │ ngx_epoll_process_events() │ │ (epoll_wait) │ └─────────────────────────────────────┘ ↓ ┌───────────────────────┴───────────────────────┐ ↓ ↓ ┌─────────────────────┐ ┌─────────────────────┐ │ 读事件就绪 │ │ 写事件就绪 │ │ EPOLLIN │ │ EPOLLOUT │ └─────────┬───────────┘ └─────────┬───────────┘ ↓ ↓ ┌─────────────────────┐ ┌─────────────────────┐ │ ngx_event_accept() │ │ ngx_http_write_ │ │ (新连接) │ │ filter() │ └─────────┬───────────┘ └─────────┬───────────┘ ↓ ↓ ┌─────────────────────┐ ┌─────────────────────┐ │ ngx_http_init_ │ │ 发送响应数据 │ │ connection() │ │ (writev/sendfile) │ └─────────┬───────────┘ └─────────┬───────────┘ ↓ ↓ ┌─────────────────────┐ ┌─────────────────────┐ │ ngx_http_process_ │ │ 数据发完? │ │ request_line() │ │ close connection │ └─────────────────────┘ └─────────────────────┘ 【多阶段事件处理】 ┌─────────────────────────────────────────────────────────────────┐ │ 事件类型 │ 处理方式 │ ├─────────────────────────────────────────────────────────────────┤ │ 连接建立 (accept) │ 由 master 或 worker 处理 │ │ 请求数据可读 (read) │ 放入 posted_accept_events │ │ 响应数据可写 (write) │ 放入 posted_events │ │ 定时器超时 (timer) │ 放入 timer_rbtree │ │ 信号事件 (signal) │ 单独信号处理线程 │ └─────────────────────────────────────────────────────────────────┘
5.3 连接池管理
/**
* Nginx 连接池结构
* 源码位置: src/core/ngx_connection.h
*/
/* 连接结构体 */
typedef struct ngx_connection_s {
void *data; /* 上层数据 */
ngx_event_t *read; /* 读事件 */
ngx_event_t *write; /* 写事件 */
ngx_socket_t fd; /* Socket 文件描述符 */
ngx_recv_pt recv; /* 接收函数 */
ngx_send_pt send; /* 发送函数 */
ngx_recv_chain_pt recv_chain; /* 链接收 */
ngx_send_chain_pt send_chain; /* 链发送 */
ngx_listening_t *listening; /* 监听器 */
off_t sent; /* 已发送字节 */
ngx_log_t *log; /* 日志 */
/* 连接状态 */
unsigned destroyed:1; /* 已销毁 */
unsigned log_error:3; /* 错误日志级别 */
/* 缓冲区 */
ngx_pool_t *pool; /* 连接专用内存池 */
ngx_chain_t *buffer; /* 缓冲区链 */
} ngx_connection_t;
/* 连接池初始化 */
static ngx_connection_t *ngx_cycle->free_connections; /* 空闲连接链表 */
ngx_connection_t *ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
{
ngx_connection_t *c;
/* 从空闲链表取连接 */
c = ngx_cycle->free_connections;
if (c == NULL) {
/* 连接数达到上限,返回错误 */
return NULL;
}
/* 移动空闲链表指针 */
ngx_cycle->free_connections = c->data;
ngx_cycle->free_connection_n--;
/* 初始化连接 */
ngx_memzero(c, sizeof(ngx_connection_t));
c->fd = s;
c->log = log;
c->pool = ngx_create_pool(4096, log); /* 4KB 连接专用池 */
return c;
}
六、性能分析与调优
6.1 性能指标监控
#!/bin/bash
# nginx_perf_monitor.sh - Nginx 性能监控脚本
# 1. 连接数统计
echo "=== 连接数统计 ==="
curl -s "http://localhost/nginx_status" 2>/dev/null | grep -E "Active|Reading|Writing|Waiting"
# 2. 请求速率统计
echo -e "\n=== 请求速率 ==="
awk '{print $4}' /var/log/nginx/access.log | cut -d: -f2 | sort | uniq -c | tail -10
# 3. 响应时间分布
echo -e "\n=== 响应时间分布 ==="
awk '{print $NF}' /var/log/nginx/access.log | cut -d'"' -f2 | \
awk '{if($1 ~ /^[0-9]+$/) print $1}' | \
awk '{if($1<100) a++; else if($1<300) b++; else if($1<1000) c++; else d++} \
END{print "Fast(<100ms): " a; print "Normal(100-300ms): " b; \
print "Slow(300-1000ms): " c; print "Very Slow(>1000ms): " d}'
# 4. 上游服务器健康检查
echo -e "\n=== 上游服务器状态 ==="
curl -s "http://localhost/upstream_status" 2>/dev/null | grep -E "server|status"
# 5. 缓存命中率
echo -e "\n=== 缓存命中率 ==="
CACHE_HIT=$(grep "HIT" /var/log/nginx/cache.log | wc -l)
CACHE_MISS=$(grep "MISS" /var/log/nginx/cache.log | wc -l)
TOTAL=$((CACHE_HIT + CACHE_MISS))
if [ $TOTAL -gt 0 ]; then
HIT_RATE=$((CACHE_HIT * 100 / TOTAL))
echo "Hit: $CACHE_HIT, Miss: $CACHE_MISS, Hit Rate: $HIT_RATE%"
fi
6.2 内核参数优化
#!/bin/bash
# kernel_tune.sh - Linux 内核参数优化 (适用于高并发HTTP服务)
# 1. TCP 参数优化
cat >> /etc/sysctl.conf << 'EOF'
# TCP 连接优化
net.ipv4.tcp_tw_reuse = 1 # TIME_WAIT 复用
net.ipv4.tcp_fin_timeout = 30 # FIN_WAIT 超时
net.ipv4.tcp_max_tw_buckets = 2000000 # TIME_WAIT 最大数量
net.ipv4.tcp_max_syn_backlog = 8192 # SYN 队列长度
net.ipv4.tcp_syn_retries = 2 # SYN 重试次数
net.ipv4.tcp_synack_retries = 2 # SYN-ACK 重试次数
# TCP 窗口优化
net.core.rmem_max = 134217728 # 接收缓冲最大 128MB
net.core.wmem_max = 134217728 # 发送缓冲最大 128MB
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
# 连接队列优化
net.core.somaxconn = 65535 # listen 队列长度
net.core.netdev_max_backlog = 65535 # 输入队列长度
# 文件描述符限制
fs.file-max = 2097152 # 最大文件描述符
EOF
# 2. 应用配置
cat >> /etc/security/limits.conf << 'EOF'
* soft nofile 655350
* hard nofile 655350
* soft nproc 655350
* hard nproc 655350
EOF
# 3. Nginx 配置优化
cat >> /etc/nginx/nginx.conf << 'EOF'
# 全局配置
worker_processes auto;
worker_rlimit_nofile 655350;
events {
use epoll;
worker_connections 65535;
multi_accept on;
accept_mutex on;
}
http {
# 保持连接优化
keepalive_timeout 65;
keepalive_requests 1000;
# 缓冲区优化
client_body_buffer_size 128k;
client_header_buffer_size 8k;
large_client_header_buffers 4 32k;
# 文件缓存
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_errors on;
# 输出缓冲
output_buffers 32 32k;
postpone_output 1460;
}
EOF
6.3 性能压测工具
#!/bin/bash
# benchmark.sh - HTTP 性能压测
# 1. 使用 ab (Apache Bench)
ab -n 100000 -c 1000 -k http://localhost/ \
-H "Accept-Encoding: gzip" \
-H "Connection: keep-alive" \
-s 30 -t 60
# 2. 使用 wrk (更现代)
wrk -t 4 -c 100 -d 30s --latency http://localhost/
# 3. 使用 hey (Go 实现)
hey -n 100000 -c 1000 -t 30 -m GET http://localhost/
# 4. 使用 locust (Python 分布式)
cat > locustfile.py << 'EOF'
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(0.1, 0.5)
@task
def index(self):
self.client.get("/")
@task(3)
def api(self):
self.client.get("/api/status")
EOF
locust -f locustfile.py --host=http://localhost --headless -u 100 -r 10 -t 60s
6.4 火焰图分析
#!/bin/bash # flamegraph.sh - 生成性能火焰图 # 1. 安装 perf sudo apt install linux-tools-common linux-tools-generic # 2. 采样 sudo perf record -F 99 -g -- nginx -s reload sudo perf record -F 99 -g -p $(pgrep -f "nginx: worker") -- sleep 30 # 3. 生成报告 sudo perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > flame.svg # 4. 查看 firefox flame.svg # 或者使用更简单的 py-spy pip install py-spy sudo py-spy record -o profile.svg -p $(pgrep -f "nginx: worker") -d 30
七、常见问题定位与调试
7.1 调试工具
#!/bin/bash # nginx_debug.sh - Nginx 调试工具集 # 1. 查看编译配置 nginx -V 2>&1 # 2. 测试配置文件 nginx -t # 3. 查看错误日志 tail -f /var/log/nginx/error.log # 4. 查看访问日志 (实时) tail -f /var/log/nginx/access.log # 5. 使用 strace 跟踪系统调用 strace -p $(pgrep -f "nginx: worker") -e trace=network,file -f # 6. 使用 gdb 调试 gdb -p $(pgrep -f "nginx: worker") # 7. 查看内存使用 pmap -x $(pgrep -f "nginx: worker") # 8. 查看打开文件 lsof -p $(pgrep -f "nginx: worker") # 9. 查看网络连接 ss -tunap | grep nginx # 10. 查看请求处理时间 (添加到日志) cat > /etc/nginx/conf.d/log_format.conf << 'EOF' log_format timing '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '$request_time $upstream_response_time ' '$upstream_addr'; access_log /var/log/nginx/timing.log timing; EOF
7.2 常见问题排查表
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
| 连接数过高 | 慢客户端/后端 | ss -s netstat -an |
调整 keepalive, 增加 worker |
| CPU 100% | 计算密集/死循环 | perf top top -H |
优化配置, 升级硬件 |
| 内存泄漏 | 缓冲区未释放 | valgrind pmap |
检查模块, 重启进程 |
| 502 Bad Gateway | 后端不可用 | tail error.log |
检查后端服务, 增加超时 |
| 504 Gateway Timeout | 后端响应慢 | tail access.log |
优化后端, 增加超时 |
| 大量 TIME_WAIT | 短连接频繁 | ss -tan state time-wait |
开启 tcp_tw_reuse |
| 文件描述符不足 | 连接数超限 | ulimit -n lsof |
调整 ulimit |
| 请求排队 | worker 不够 | nginx -T 查看配置 |
增加 worker_processes |
7.3 性能基准参考
【Nginx 性能基准参考 (单核 Intel Xeon)】 ┌─────────────────┬─────────────────┬─────────────────┬─────────────────┐ │ 场景 │ QPS (静态) │ QPS (代理) │ QPS (动态) │ ├─────────────────┼─────────────────┼─────────────────┼─────────────────┤ │ 静态文件 │ 50,000+ │ N/A │ N/A │ │ 反向代理 │ 20,000-30,000 │ 15,000-20,000 │ 10,000-15,000 │ │ FastCGI (PHP) │ 3,000-5,000 │ N/A │ N/A │ │ 开启 SSL │ 3,000-5,000 │ 2,000-3,000 │ 1,500-2,500 │ │ HTTP/2 │ 8,000-10,000 │ 6,000-8,000 │ 4,000-6,000 │ └─────────────────┴─────────────────┴─────────────────┴─────────────────┘ 【内存占用参考】 ┌─────────────────────────────────────────────────────────────────┐ │ 配置 │ 每个 worker 内存 │ 总内存 (4 worker) │ ├─────────────────┼─────────────────┼─────────────────────────────┤ │ 最小配置 │ 约 10 MB │ 约 50 MB │ │ 默认配置 │ 约 30 MB │ 约 150 MB │ │ 大量 keepalive │ 约 50-100 MB │ 约 200-400 MB │ │ 缓存配置 │ 约 100-500 MB │ 约 400 MB - 2 GB │ └─────────────────┴─────────────────┴─────────────────────────────┘
八、总结
| 维度 | Nginx | Envoy | HAProxy |
|---|---|---|---|
| 架构 | 多进程 (master+worker) | 多线程 (thread-per-core) | 多进程 (单线程事件驱动) |
| 事件模型 | epoll/kqueue (Reactor) | epoll (Reactor) | epoll (Reactor) |
| 内存管理 | 内存池 (ngx_pool_t) | 引用计数 + 内存池 | 固定缓冲区 |
| 配置语言 | Nginx 声明式 | YAML | 声明式 |
| HTTP/2 | 原生支持 | 原生支持 | 需要配置 |
| gRPC | 支持 | 原生支持 | 支持 |
| 扩展性 | 模块 (C) | 过滤器 (C++) | Lua 脚本 |
| 适用场景 | Web 服务器、反向代理 | 云原生、Service Mesh | 负载均衡、高可用 |
HTTP 中间件核心要点:
-
事件驱动 - 所有开源中间件都基于 epoll/kqueue 实现高并发
-
内存池 - Nginx 的内存池设计避免了频繁分配/释放
-
零拷贝 - sendfile 技术直接发送文件到 socket
-
阶段处理 - Nginx 的 11 个处理阶段实现模块化
-
连接复用 - Keep-Alive 大幅提升性能
第二部分 HTTP 中间件嵌入式部署与文件传输全链路分析
/** * @file http_embedded_deployment.c * @brief HTTP 库在嵌入式目标板的部署结构 + 文件上传/下载全流程 * * 内容涵盖: * 1. libcurl/libmicrohttpd 在嵌入式板子的目录树形结构 * 2. HTTP 文件上传全流程(C 语言实现 + 树形分析) * 3. HTTP 文件下载全流程(C 语言实现 + 树形分析) * 4. 断点续传的多种实现手段 * 5. 通过路由设备与其他 IP 通信(NAT/端口转发/UPnP) */
一、HTTP 库在嵌入式目标板的部署结构
1.1 libcurl 部署目录树
【嵌入式目标板 - libcurl 安装目录树】 / ├── usr/ │ ├── lib/ │ │ ├── libcurl.so -> libcurl.so.4.8.0 │ │ ├── libcurl.so.4 -> libcurl.so.4.8.0 │ │ ├── libcurl.so.4.8.0 # 动态库 (约 800KB-1.2MB) │ │ ├── libcurl.a # 静态库 (约 1.5-2.5MB,调试版更大) │ │ └── pkgconfig/ │ │ └── libcurl.pc # pkg-config 配置 │ │ │ ├── include/ │ │ └── curl/ │ │ ├── curl.h # 主头文件 │ │ ├── curlver.h # 版本定义 │ │ ├── easy.h # 简易接口 │ │ ├── multi.h # 多接口 │ │ ├── options.h # 选项定义 │ │ ├── typecheck-gcc.h # 类型检查 │ │ ├── mprintf.h # printf 扩展 │ │ ├── stdcheaders.h # 标准头包含 │ │ └── system.h # 系统适配 │ │ │ ├── bin/ │ │ └── curl # curl 命令行工具 (约 200KB) │ │ │ └── share/ │ └── man/ │ └── man1/ │ └── curl.1.gz # 帮助手册 │ ├── etc/ │ └── ld.so.conf.d/ │ └── curl.conf # 动态链接器配置 │ └── lib/ └── libcurl.so.4 -> /usr/lib/libcurl.so.4 # 符号链接
1.2 libmicrohttpd 部署目录树
【嵌入式目标板 - libmicrohttpd 安装目录树】 / ├── usr/ │ ├── lib/ │ │ ├── libmicrohttpd.so -> libmicrohttpd.so.12.58.0 │ │ ├── libmicrohttpd.so.12 -> libmicrohttpd.so.12.58.0 │ │ ├── libmicrohttpd.so.12.58.0 # 动态库 (约 400-600KB) │ │ ├── libmicrohttpd.a # 静态库 (约 800KB-1.2MB) │ │ └── pkgconfig/ │ │ └── libmicrohttpd.pc │ │ │ ├── include/ │ │ └── microhttpd.h # 主头文件 (MHD API) │ │ │ ├── bin/ │ │ └── microhttpd-test # 测试程序 (可选) │ │ │ └── share/ │ └── doc/ │ └── libmicrohttpd/ │ ├── tutorial.html │ └── examples/ # 示例代码 │ └── etc/ └── microhttpd/ └── mime.types # MIME 类型配置
1.3 交叉编译脚本示例
#!/bin/bash # cross_compile_http.sh - 交叉编译 HTTP 库到 ARM 目标板 # 环境变量配置 export TOOLCHAIN=/opt/gcc-arm-10.2-2020.11-x86_64-arm-none-linux-gnueabihf export CC=$TOOLCHAIN/bin/arm-none-linux-gnueabihf-gcc export CXX=$TOOLCHAIN/bin/arm-none-linux-gnueabihf-g++ export AR=$TOOLCHAIN/bin/arm-none-linux-gnueabihf-ar export LD=$TOOLCHAIN/bin/arm-none-linux-gnueabihf-ld export STRIP=$TOOLCHAIN/bin/arm-none-linux-gnueabihf-strip export CFLAGS="-Os -fPIC -flto" export LDFLAGS="-Wl,-O1 -Wl,--as-needed" # 目标板 sysroot export SYSROOT=$TOOLCHAIN/arm-none-linux-gnueabihf/libc export INSTALL_DIR=/tmp/http_libs_install # 1. 编译 OpenSSL (HTTPS 支持) echo "=== Building OpenSSL ===" cd /tmp wget https://www.openssl.org/source/openssl-3.0.12.tar.gz tar xzf openssl-3.0.12.tar.gz cd openssl-3.0.12 ./Configure linux-generic32 \ --cross-compile-prefix=arm-none-linux-gnueabihf- \ --prefix=$INSTALL_DIR/openssl \ --openssldir=$INSTALL_DIR/openssl/ssl \ no-shared \ no-asm make -j4 && make install # 2. 编译 libcurl (客户端) echo "=== Building libcurl ===" cd /tmp wget https://curl.se/download/curl-8.5.0.tar.gz tar xzf curl-8.5.0.tar.gz cd curl-8.5.0 ./configure \ --host=arm-none-linux-gnueabihf \ --prefix=$INSTALL_DIR/curl \ --with-sysroot=$SYSROOT \ --with-ssl=$INSTALL_DIR/openssl \ --enable-static \ --disable-shared \ --disable-ldap \ --disable-ldaps \ --disable-rtsp \ --disable-dict \ --disable-telnet \ --disable-tftp \ --disable-pop3 \ --disable-imap \ --disable-smtp \ --disable-gopher \ --disable-mqtt \ --disable-manual \ --disable-verbose \ --disable-versioned-symbols \ --enable-symbol-hiding \ --without-libidn2 \ --without-brotli \ --without-zstd make -j4 && make install # 3. 编译 libmicrohttpd (服务端) echo "=== Building libmicrohttpd ===" cd /tmp wget https://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-1.0.1.tar.gz tar xzf libmicrohttpd-1.0.1.tar.gz cd libmicrohttpd-1.0.1 ./configure \ --host=arm-none-linux-gnueabihf \ --prefix=$INSTALL_DIR/mhd \ --with-sysroot=$SYSROOT \ --enable-static \ --disable-shared \ --disable-https \ --disable-curl \ --disable-dauth \ --disable-messages \ --disable-postprocessor \ --disable-docs make -j4 && make install # 4. 打包最终库文件 echo "=== Packaging ===" cd $INSTALL_DIR tar czf http_libs_arm.tar.gz curl/ mhd/ openssl/ # 5. 部署到目标板 # scp http_libs_arm.tar.gz root@192.168.1.100:/tmp/ # 在目标板上: # tar xzf /tmp/http_libs_arm.tar.gz -C /usr/
1.4 目标板库依赖检查
# 在目标板上检查库依赖 root@board:~# ls -la /usr/lib/libcurl* -rw-r--r-- 1 root root 1523456 Mar 25 10:30 libcurl.a -rwxr-xr-x 1 root root 985432 Mar 25 10:30 libcurl.so.4.8.0 lrwxrwxrwx 1 root root 19 Mar 25 10:30 libcurl.so.4 -> libcurl.so.4.8.0 lrwxrwxrwx 1 root root 19 Mar 25 10:30 libcurl.so -> libcurl.so.4.8.0 root@board:~# ldd /usr/lib/libcurl.so.4.8.0 linux-vdso.so.1 (0x7efdf000) libssl.so.3 => /usr/lib/libssl.so.3 (0x7ef8e000) libcrypto.so.3 => /usr/lib/libcrypto.so.3 (0x7edc0000) libz.so.1 => /usr/lib/libz.so.1 (0x7ed9d000) libc.so.6 => /lib/libc.so.6 (0x7ec5c000) libdl.so.2 => /lib/libdl.so.2 (0x7ec4e000) libpthread.so.0 => /lib/libpthread.so.0 (0x7ec2b000) /lib/ld-linux-armhf.so.3 (0x7efee000) root@board:~# ls -la /usr/include/curl/ total 120 drwxr-xr-x 2 root root 4096 Mar 25 10:30 . drwxr-xr-x 3 root root 4096 Mar 25 10:30 .. -rw-r--r-- 1 root root 37280 Mar 25 10:30 curl.h -rw-r--r-- 1 root root 5543 Mar 25 10:30 curlver.h -rw-r--r-- 1 root root 4265 Mar 25 10:30 easy.h -rw-r--r-- 1 root root 8521 Mar 25 10:30 multi.h -rw-r--r-- 1 root root 29624 Mar 25 10:30 options.h -rw-r--r-- 1 root root 3348 Mar 25 10:30 typecheck-gcc.h -rw-r--r-- 1 root root 1512 Mar 25 10:30 mprintf.h -rw-r--r-- 1 root root 1192 Mar 25 10:30 stdcheaders.h -rw-r--r-- 1 root root 3200 Mar 25 10:30 system.h
二、HTTP 文件上传全流程 (C 语言实现)
2.1 上传流程树形分析
【HTTP 文件上传完整流程树】
┌─────────────────────────────────────────────────────────────────┐
│ 1. 初始化阶段 (Upload Init) │
├─────────────────────────────────────────────────────────────────┤
│ curl_global_init(CURL_GLOBAL_ALL) │
│ ├── 初始化 SSL 库 (OpenSSL) │
│ ├── 初始化 DNS 缓存 │
│ └── 初始化内存分配器 │
│ │
│ curl_easy_init() │
│ ├── 分配 CURL 句柄 │
│ ├── 创建连接池 │
│ └── 设置默认选项 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. 配置阶段 (Upload Config) │
├─────────────────────────────────────────────────────────────────┤
│ curl_easy_setopt(CURLOPT_URL, "http://server/upload") │
│ └── 解析 URL (协议/主机/端口/路径) │
│ │
│ curl_easy_setopt(CURLOPT_UPLOAD, 1L) │
│ └── 设置为上传模式 │
│ │
│ curl_easy_setopt(CURLOPT_READFUNCTION, read_callback) │
│ └── 注册数据读取回调 │
│ │
│ curl_easy_setopt(CURLOPT_READDATA, &upload_ctx) │
│ └── 传递文件句柄 │
│ │
│ curl_easy_setopt(CURLOPT_INFILESIZE_LARGE, file_size) │
│ └── 设置文件大小 (用于进度显示和服务器验证) │
│ │
│ curl_easy_setopt(CURLOPT_HTTPHEADER, headers) │
│ └── 添加自定义头 (Content-Type, X-File-Name) │
│ │
│ curl_easy_setopt(CURLOPT_NOPROGRESS, 0L) │
│ curl_easy_setopt(CURLOPT_XFERINFOFUNCTION, progress_callback) │
│ └── 设置进度回调 │
│ │
│ curl_easy_setopt(CURLOPT_VERBOSE, 1L) │
│ └── 开启调试输出 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. DNS 解析阶段 │
├─────────────────────────────────────────────────────────────────┤
│ curl_easy_perform() 内部调用 │
│ ├── getaddrinfo("server", "80", ...) │
│ │ ├── 检查本地 hosts 文件 │
│ │ ├── DNS 查询 (UDP 53) │
│ │ └── 获取 IP 地址列表 │
│ └── 选择可用 IP (轮询/优先) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. TCP 连接阶段 │
├─────────────────────────────────────────────────────────────────┤
│ socket(AF_INET, SOCK_STREAM, 0) │
│ ├── 创建 socket 文件描述符 │
│ └── 设置 TCP_NODELAY (禁用 Nagle) │
│ │
│ connect(fd, (struct sockaddr*)&addr, sizeof(addr)) │
│ ├── 发送 SYN 包 │
│ ├── 等待 SYN-ACK │
│ ├── 发送 ACK │
│ └── TCP 三次握手完成 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 5. TLS/SSL 握手阶段 (HTTPS) │
├─────────────────────────────────────────────────────────────────┤
│ SSL_CTX_new(TLS_client_method()) │
│ ├── 加载 CA 证书 │
│ ├── 设置验证模式 │
│ └── 创建 SSL 会话 │
│ │
│ SSL_connect(ssl) │
│ ├── ClientHello → 发送支持的加密套件 │
│ ├── ServerHello ← 服务器选择 │
│ ├── 证书验证 │
│ ├── 密钥交换 (ECDHE/RSA) │
│ └── ChangeCipherSpec + Finished │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 6. HTTP 请求发送阶段 │
├─────────────────────────────────────────────────────────────────┤
│ send_request() │
│ ├── 发送请求行: │
│ │ PUT /upload/file.dat HTTP/1.1 │
│ ├── 发送请求头: │
│ │ Host: server.example.com │
│ │ User-Agent: libcurl/8.5.0 │
│ │ Accept: */* │
│ │ Content-Type: application/octet-stream │
│ │ Content-Length: 1048576 │
│ │ Expect: 100-continue │
│ │ X-File-Name: test.dat │
│ │ X-File-Size: 1048576 │
│ │ X-File-MD5: d41d8cd98f00b204e9800998ecf8427e │
│ └── 空行 (\r\n) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 7. 等待 100 Continue (可选) │
├─────────────────────────────────────────────────────────────────┤
│ recv() 读取服务器响应 │
│ ├── 收到 "HTTP/1.1 100 Continue" → 继续发送 │
│ └── 收到 "HTTP/1.1 413 Payload Too Large" → 中止 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 8. 数据发送阶段 (核心) │
├─────────────────────────────────────────────────────────────────┤
│ while (bytes_sent < total_size) { │
│ ├── read_callback() 从文件读取数据 │
│ │ ├── fread(buffer, 1, chunk_size, fp) │
│ │ └── 返回数据指针和长度 │
│ │ │
│ ├── SSL_write(ssl, buffer, len) 或 send(fd, ...) │
│ │ ├── TCP 发送缓冲区 │
│ │ ├── 内核协议栈 │
│ │ └── 网卡发送 │
│ │ │
│ ├── progress_callback() 更新进度 │
│ │ └── 计算速度、剩余时间 │
│ │ │
│ └── 检查是否中断 (用户取消/网络错误) │
│ } │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 9. 接收响应阶段 │
├─────────────────────────────────────────────────────────────────┤
│ recv_response() │
│ ├── 读取状态行: │
│ │ HTTP/1.1 200 OK │
│ ├── 读取响应头: │
│ │ Server: nginx/1.24.0 │
│ │ Content-Type: application/json │
│ │ Location: /files/test.dat │
│ │ X-Upload-Status: success │
│ │ X-File-MD5: d41d8cd98f00b204e9800998ecf8427e │
│ ├── 读取响应体 (JSON 响应) │
│ │ {"status":"success","url":"/files/test.dat","size":1048576}│
│ └── 关闭连接或复用 (keep-alive) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 10. 清理阶段 │
├─────────────────────────────────────────────────────────────────┤
│ curl_easy_cleanup() │
│ ├── 关闭连接 │
│ ├── 释放 SSL 上下文 │
│ ├── 释放内存池 │
│ └── 关闭文件句柄 │
│ │
│ curl_global_cleanup() │
│ └── 释放全局资源 │
└─────────────────────────────────────────────────────────────────┘
2.2 完整 C 语言上传代码
/**
* http_upload.c - HTTP 文件上传完整实现
* 编译: arm-none-linux-gnueabihf-gcc -o http_upload http_upload.c -lcurl
* 运行: ./http_upload http://server/upload ./file.dat
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <sys/stat.h>
#include <time.h>
/* 上传上下文 */
typedef struct {
FILE *fp; /* 文件指针 */
size_t total_size; /* 文件总大小 */
size_t uploaded; /* 已上传大小 */
char filename[256]; /* 文件名 */
char md5[33]; /* MD5 值 */
double start_time; /* 开始时间 */
} upload_context_t;
/* 读取回调函数 - 提供给 libcurl 读取数据 */
static size_t read_callback(char *buffer, size_t size, size_t nitems, void *userdata)
{
upload_context_t *ctx = (upload_context_t *)userdata;
size_t bytes_to_read = size * nitems;
size_t bytes_read;
if (!ctx->fp) {
return 0;
}
bytes_read = fread(buffer, 1, bytes_to_read, ctx->fp);
ctx->uploaded += bytes_read;
return bytes_read;
}
/* 进度回调函数 */
static int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
{
upload_context_t *ctx = (upload_context_t *)clientp;
double now = (double)time(NULL);
double elapsed = now - ctx->start_time;
double speed = (elapsed > 0) ? ctx->uploaded / elapsed : 0;
int percent = (ctx->total_size > 0) ? (int)(ctx->uploaded * 100 / ctx->total_size) : 0;
printf("\r[%3d%%] %.2f MB / %.2f MB | %.2f KB/s",
percent,
ctx->uploaded / (1024.0 * 1024.0),
ctx->total_size / (1024.0 * 1024.0),
speed / 1024.0);
fflush(stdout);
return 0; /* 返回非0会中止传输 */
}
/* 计算文件 MD5 */
static void compute_md5(const char *filename, char *md5_out)
{
FILE *fp = fopen(filename, "rb");
if (!fp) return;
/* 使用 OpenSSL MD5 (需要链接 -lssl -lcrypto) */
// EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
// EVP_DigestInit_ex(mdctx, EVP_md5(), NULL);
// ...
fclose(fp);
/* 示例: 固定 MD5 (实际应真实计算) */
strcpy(md5_out, "d41d8cd98f00b204e9800998ecf8427e");
}
/* 获取文件大小 */
static size_t get_file_size(const char *filename)
{
struct stat st;
if (stat(filename, &st) == 0) {
return st.st_size;
}
return 0;
}
/* 构建请求头 */
static struct curl_slist *build_headers(const char *filename, const char *md5, size_t size)
{
struct curl_slist *headers = NULL;
char header[256];
/* 添加自定义头 */
snprintf(header, sizeof(header), "X-File-Name: %s", filename);
headers = curl_slist_append(headers, header);
snprintf(header, sizeof(header), "X-File-Size: %zu", size);
headers = curl_slist_append(headers, header);
snprintf(header, sizeof(header), "X-File-MD5: %s", md5);
headers = curl_slist_append(headers, header);
headers = curl_slist_append(headers, "Content-Type: application/octet-stream");
headers = curl_slist_append(headers, "Expect: 100-continue");
return headers;
}
/**
* HTTP 文件上传主函数
* @param url 上传目标 URL
* @param filename 本地文件路径
* @return 0 成功,-1 失败
*/
int http_upload_file(const char *url, const char *filename)
{
CURL *curl;
CURLcode res;
upload_context_t ctx = {0};
struct curl_slist *headers = NULL;
printf("=== HTTP 文件上传 ===\n");
printf("目标: %s\n", url);
printf("文件: %s\n", filename);
/* 1. 打开文件 */
ctx.fp = fopen(filename, "rb");
if (!ctx.fp) {
fprintf(stderr, "错误: 无法打开文件 %s\n", filename);
return -1;
}
/* 2. 获取文件信息 */
ctx.total_size = get_file_size(filename);
strncpy(ctx.filename, filename, sizeof(ctx.filename) - 1);
compute_md5(filename, ctx.md5);
ctx.start_time = (double)time(NULL);
printf("大小: %.2f MB\n", ctx.total_size / (1024.0 * 1024.0));
printf("MD5: %s\n", ctx.md5);
/* 3. 初始化 libcurl */
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (!curl) {
fclose(ctx.fp);
fprintf(stderr, "错误: curl_easy_init 失败\n");
return -1;
}
/* 4. 构建请求头 */
headers = build_headers(ctx.filename, ctx.md5, ctx.total_size);
/* 5. 设置 libcurl 选项 */
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
curl_easy_setopt(curl, CURLOPT_READDATA, &ctx);
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)ctx.total_size);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &ctx);
/* 超时设置 */
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3600L);
/* 6. 执行上传 */
printf("\n上传中...\n");
res = curl_easy_perform(curl);
/* 7. 检查结果 */
if (res != CURLE_OK) {
fprintf(stderr, "\n错误: curl_easy_perform 失败: %s\n", curl_easy_strerror(res));
} else {
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code == 200 || http_code == 201) {
printf("\n✓ 上传成功!\n");
} else {
printf("\n✗ 服务器返回错误: %ld\n", http_code);
}
}
/* 8. 清理 */
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
fclose(ctx.fp);
return (res == CURLE_OK) ? 0 : -1;
}
int main(int argc, char *argv[])
{
if (argc < 3) {
fprintf(stderr, "用法: %s <url> <filename>\n", argv[0]);
fprintf(stderr, "示例: %s http://192.168.1.100:8080/upload test.dat\n", argv[0]);
return 1;
}
return http_upload_file(argv[1], argv[2]);
}
三、HTTP 文件下载全流程 (C 语言实现)
3.1 下载流程树形分析
【HTTP 文件下载完整流程树】
┌─────────────────────────────────────────────────────────────────┐
│ 1. 初始化阶段 (Download Init) │
├─────────────────────────────────────────────────────────────────┤
│ curl_global_init(CURL_GLOBAL_ALL) │
│ curl_easy_init() │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. 配置阶段 (Download Config) │
├─────────────────────────────────────────────────────────────────┤
│ curl_easy_setopt(CURLOPT_URL, "http://server/file.dat") │
│ curl_easy_setopt(CURLOPT_WRITEFUNCTION, write_callback) │
│ curl_easy_setopt(CURLOPT_WRITEDATA, &download_ctx) │
│ curl_easy_setopt(CURLOPT_RESUME_FROM_LARGE, resume_position) │
│ └── 断点续传起点 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. 连接 + HTTP 请求 │
├─────────────────────────────────────────────────────────────────┤
│ GET /file.dat HTTP/1.1 │
│ Host: server.example.com │
│ Range: bytes=1048576- │
│ └── 断点续传关键头 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. 接收响应 │
├─────────────────────────────────────────────────────────────────┤
│ HTTP/1.1 206 Partial Content │
│ Content-Range: bytes 1048576-2097151/2097152 │
│ Content-Length: 1048576 │
│ └── 206 状态码表示部分内容 │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 5. 数据接收循环 │
├─────────────────────────────────────────────────────────────────┤
│ while (下载未完成) { │
│ recv() / SSL_read() │
│ write_callback() 写入文件 │
│ ├── fwrite() 到本地文件 │
│ ├── 更新已下载大小 │
│ └── 保存断点信息 (每 1MB) │
│ } │
└─────────────────────────────────────────────────────────────────┘
3.2 完整 C 语言下载代码
/**
* http_download.c - HTTP 文件下载完整实现 (支持断点续传)
* 编译: arm-none-linux-gnueabihf-gcc -o http_download http_download.c -lcurl
* 运行: ./http_download http://server/file.dat ./download.dat
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <sys/stat.h>
#include <unistd.h>
/* 下载上下文 */
typedef struct {
FILE *fp; /* 文件指针 */
size_t downloaded; /* 已下载大小 */
size_t total_size; /* 总大小 (从 Content-Length 获取) */
char resume_file[256]; /* 断点信息保存文件 */
double start_time; /* 开始时间 */
int last_percent; /* 上次进度百分比 */
} download_context_t;
/* 写回调函数 - 保存下载数据到文件 */
static size_t write_callback(char *buffer, size_t size, size_t nmemb, void *userdata)
{
download_context_t *ctx = (download_context_t *)userdata;
size_t bytes = size * nmemb;
size_t written;
if (!ctx->fp) {
return 0;
}
written = fwrite(buffer, 1, bytes, ctx->fp);
ctx->downloaded += written;
/* 每 1MB 保存一次断点信息 */
if (ctx->resume_file[0] && (ctx->downloaded % (1024 * 1024) < bytes)) {
FILE *resume_fp = fopen(ctx->resume_file, "w");
if (resume_fp) {
fprintf(resume_fp, "%zu\n", ctx->downloaded);
fclose(resume_fp);
}
}
/* 显示进度 */
if (ctx->total_size > 0) {
int percent = (int)(ctx->downloaded * 100 / ctx->total_size);
if (percent != ctx->last_percent) {
double elapsed = (double)time(NULL) - ctx->start_time;
double speed = (elapsed > 0) ? ctx->downloaded / elapsed : 0;
printf("\r[%3d%%] %.2f MB / %.2f MB | %.2f KB/s",
percent,
ctx->downloaded / (1024.0 * 1024.0),
ctx->total_size / (1024.0 * 1024.0),
speed / 1024.0);
fflush(stdout);
ctx->last_percent = percent;
}
}
return written;
}
/* 获取已下载进度 (从断点文件读取) */
static size_t get_resume_position(const char *resume_file)
{
FILE *fp = fopen(resume_file, "r");
size_t pos = 0;
if (fp) {
fscanf(fp, "%zu", &pos);
fclose(fp);
}
return pos;
}
/* 保存断点信息 */
static void save_resume_info(download_context_t *ctx)
{
if (ctx->resume_file[0]) {
FILE *fp = fopen(ctx->resume_file, "w");
if (fp) {
fprintf(fp, "%zu\n", ctx->downloaded);
fclose(fp);
}
}
}
/* 检查文件是否存在,获取大小 */
static size_t get_existing_file_size(const char *filename)
{
struct stat st;
if (stat(filename, &st) == 0) {
return st.st_size;
}
return 0;
}
/* 打开文件用于写入 (支持断点续传模式) */
static FILE *open_file_for_resume(const char *filename, size_t resume_pos, int *is_new)
{
FILE *fp;
if (resume_pos > 0) {
/* 断点续传模式: 追加写入 */
fp = fopen(filename, "ab");
if (fp) {
*is_new = 0;
printf("断点续传: 从 %zu 字节继续\n", resume_pos);
}
}
if (!fp) {
/* 新文件模式 */
fp = fopen(filename, "wb");
*is_new = 1;
}
return fp;
}
/**
* HTTP 文件下载主函数 (支持断点续传)
* @param url 下载源 URL
* @param filename 本地保存路径
* @param resume 是否启用断点续传
* @return 0 成功,-1 失败
*/
int http_download_file(const char *url, const char *filename, int resume)
{
CURL *curl;
CURLcode res;
download_context_t ctx = {0};
size_t resume_pos = 0;
int is_new_file = 1;
long http_code = 0;
double content_length = 0;
char resume_file[256];
printf("=== HTTP 文件下载 ===\n");
printf("来源: %s\n", url);
printf("保存: %s\n", filename);
/* 断点续传文件路径 */
snprintf(ctx.resume_file, sizeof(ctx.resume_file), "%s.resume", filename);
/* 获取断点位置 */
if (resume) {
resume_pos = get_resume_position(ctx.resume_file);
if (resume_pos > 0) {
/* 验证已下载文件大小 */
size_t existing = get_existing_file_size(filename);
if (existing != resume_pos) {
printf("警告: 断点文件与本地文件大小不匹配,重新开始\n");
resume_pos = 0;
unlink(ctx.resume_file);
}
}
}
/* 打开文件 */
ctx.fp = open_file_for_resume(filename, resume_pos, &is_new_file);
if (!ctx.fp) {
fprintf(stderr, "错误: 无法打开文件 %s\n", filename);
return -1;
}
/* 如果断点续传,跳转到正确位置 */
if (resume_pos > 0) {
fseek(ctx.fp, 0, SEEK_END);
}
ctx.downloaded = resume_pos;
ctx.start_time = (double)time(NULL);
ctx.last_percent = -1;
/* 初始化 libcurl */
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (!curl) {
fclose(ctx.fp);
fprintf(stderr, "错误: curl_easy_init 失败\n");
return -1;
}
/* 设置选项 */
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ctx);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); /* 跟随重定向 */
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3600L);
/* 断点续传: 设置 Range 头 */
if (resume_pos > 0) {
char range[64];
snprintf(range, sizeof(range), "%zu-", resume_pos);
curl_easy_setopt(curl, CURLOPT_RANGE, range);
printf("Range: bytes=%s\n", range);
}
/* 执行下载 */
printf("\n下载中...\n");
res = curl_easy_perform(curl);
/* 获取响应信息 */
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length);
if (content_length > 0) {
ctx.total_size = (size_t)(resume_pos + content_length);
}
/* 检查结果 */
if (res == CURLE_OK) {
if (http_code == 200 || http_code == 206) {
printf("\n✓ 下载完成!\n");
/* 下载完成,删除断点文件 */
unlink(ctx.resume_file);
} else {
printf("\n✗ 服务器返回错误: %ld\n", http_code);
}
} else {
if (res == CURLE_PARTIAL_FILE) {
printf("\n⚠ 部分下载完成,已保存断点信息\n");
save_resume_info(&ctx);
} else {
fprintf(stderr, "\n错误: %s\n", curl_easy_strerror(res));
}
}
/* 清理 */
curl_easy_cleanup(curl);
curl_global_cleanup();
fclose(ctx.fp);
return (res == CURLE_OK) ? 0 : -1;
}
int main(int argc, char *argv[])
{
int resume = 0;
if (argc < 3) {
fprintf(stderr, "用法: %s <url> <filename> [-r]\n", argv[0]);
fprintf(stderr, " -r 启用断点续传\n");
fprintf(stderr, "示例: %s http://192.168.1.100:8080/file.dat download.dat\n", argv[0]);
fprintf(stderr, " %s http://192.168.1.100:8080/file.dat download.dat -r\n", argv[0]);
return 1;
}
if (argc >= 4 && strcmp(argv[3], "-r") == 0) {
resume = 1;
}
return http_download_file(argv[1], argv[2], resume);
}
四、断点续传的多种实现手段
4.1 断点续传手段对比表
┌─────────────────┬───────────────────────────────────────────────────────┐ │ 手段 │ 实现原理 │ ├─────────────────┼───────────────────────────────────────────────────────┤ │ HTTP Range │ 发送 Range: bytes=start- 头,服务器返回 206 状态码 │ │ │ 最标准,所有现代 HTTP 服务器都支持 │ ├─────────────────┼───────────────────────────────────────────────────────┤ │ FTP REST │ 发送 REST offset 命令,再 RETR filename │ │ │ 传统 FTP 协议支持 │ ├─────────────────┼───────────────────────────────────────────────────────┤ │ 客户端状态保存 │ 本地记录已下载字节数,写入 .resume 文件 │ │ │ 每次启动时读取,设置 Range 头 │ ├─────────────────┼───────────────────────────────────────────────────────┤ │ 服务端分片索引 │ 服务器记录每个客户端的进度,通过 Token 恢复 │ │ │ 适用于多用户场景,需要服务器支持 │ ├─────────────────┼───────────────────────────────────────────────────────┤ │ 多线程分片下载 │ 将文件分成多个部分,每个线程独立下载 │ │ │ 可加速下载,同时支持断点续传 │ ├─────────────────┼───────────────────────────────────────────────────────┤ │ WebDAV │ 使用 PROPFIND 获取文件大小,使用 GET 带 Range │ │ │ 云存储常用 │ ├─────────────────┼───────────────────────────────────────────────────────┤ │ 自定义协议 │ 客户端发送位置信息,服务器从指定位置发送 │ │ │ 适用于私有协议 │ └─────────────────┴───────────────────────────────────────────────────────┘
4.2 多线程分片下载实现
/**
* multi_thread_download.c - 多线程分片下载 (支持断点续传)
* 编译: gcc -o multi_download multi_thread_download.c -lcurl -lpthread
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <curl/curl.h>
#define THREAD_COUNT 4 /* 下载线程数 */
#define CHUNK_SIZE (1024 * 1024) /* 每块 1MB,用于保存进度 */
/* 分片信息 */
typedef struct {
int id; /* 分片 ID */
size_t start; /* 起始位置 */
size_t end; /* 结束位置 (0 表示到文件尾) */
size_t downloaded; /* 已下载大小 */
FILE *fp; /* 文件指针 */
char url[256]; /* 下载 URL */
int completed; /* 是否完成 */
pthread_mutex_t lock; /* 互斥锁 */
} chunk_t;
/* 分片下载线程函数 */
static void *download_chunk(void *arg)
{
chunk_t *chunk = (chunk_t *)arg;
CURL *curl;
CURLcode res;
char range[64];
size_t local_downloaded = chunk->downloaded;
FILE *fp;
curl = curl_easy_init();
if (!curl) return NULL;
/* 设置 URL */
curl_easy_setopt(curl, CURLOPT_URL, chunk->url);
/* 设置 Range 头 */
if (chunk->end > 0) {
snprintf(range, sizeof(range), "%zu-%zu",
chunk->start + local_downloaded, chunk->end);
} else {
snprintf(range, sizeof(range), "%zu-",
chunk->start + local_downloaded);
}
curl_easy_setopt(curl, CURLOPT_RANGE, range);
/* 设置写回调 */
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, chunk);
/* 打开文件并定位到分片起始位置 */
fp = fopen("download.dat", "rb+");
if (fp) {
fseek(fp, chunk->start + local_downloaded, SEEK_SET);
} else {
fp = fopen("download.dat", "wb");
}
chunk->fp = fp;
/* 执行下载 */
res = curl_easy_perform(curl);
if (res == CURLE_OK) {
pthread_mutex_lock(&chunk->lock);
chunk->completed = 1;
pthread_mutex_unlock(&chunk->lock);
}
fclose(fp);
curl_easy_cleanup(curl);
return NULL;
}
/* 分片写入回调 */
static size_t write_chunk_data(char *buffer, size_t size, size_t nmemb, void *userdata)
{
chunk_t *chunk = (chunk_t *)userdata;
size_t bytes = size * nmemb;
if (chunk->fp) {
fwrite(buffer, 1, bytes, chunk->fp);
chunk->downloaded += bytes;
return bytes;
}
return 0;
}
/* 启动多线程分片下载 */
int multi_thread_download(const char *url)
{
pthread_t threads[THREAD_COUNT];
chunk_t chunks[THREAD_COUNT];
size_t file_size = get_remote_file_size(url); /* 获取远程文件大小 */
size_t chunk_len = file_size / THREAD_COUNT;
int i;
/* 预分配文件 */
FILE *fp = fopen("download.dat", "wb");
ftruncate(fileno(fp), file_size);
fclose(fp);
/* 创建分片 */
for (i = 0; i < THREAD_COUNT; i++) {
chunks[i].id = i;
chunks[i].start = i * chunk_len;
chunks[i].end = (i == THREAD_COUNT - 1) ? 0 : (i + 1) * chunk_len;
chunks[i].downloaded = 0;
chunks[i].completed = 0;
strcpy(chunks[i].url, url);
pthread_mutex_init(&chunks[i].lock, NULL);
pthread_create(&threads[i], NULL, download_chunk, &chunks[i]);
}
/* 等待所有线程完成 */
for (i = 0; i < THREAD_COUNT; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
4.3 断点续传的断点文件格式
/**
* resume_file.h - 断点文件格式定义
*/
/* 断点信息结构体 */
typedef struct {
char url[512]; /* 下载 URL */
char local_file[256]; /* 本地文件路径 */
size_t downloaded; /* 已下载字节数 */
size_t total_size; /* 总大小 (0 表示未知) */
time_t last_update; /* 最后更新时间 */
char etag[128]; /* ETag 用于验证文件是否变化 */
char last_modified[64]; /* Last-Modified 时间 */
unsigned char md5[16]; /* 已下载部分的 MD5 (可选) */
} resume_info_t;
/* 保存断点信息 */
int save_resume_info(const char *filename, resume_info_t *info)
{
FILE *fp = fopen(filename, "w");
if (!fp) return -1;
fprintf(fp, "# HTTP Download Resume File\n");
fprintf(fp, "# Format: key=value\n");
fprintf(fp, "url=%s\n", info->url);
fprintf(fp, "local_file=%s\n", info->local_file);
fprintf(fp, "downloaded=%zu\n", info->downloaded);
fprintf(fp, "total_size=%zu\n", info->total_size);
fprintf(fp, "last_update=%ld\n", info->last_update);
fprintf(fp, "etag=%s\n", info->etag);
fprintf(fp, "last_modified=%s\n", info->last_modified);
fclose(fp);
return 0;
}
/* 加载断点信息 */
int load_resume_info(const char *filename, resume_info_t *info)
{
FILE *fp = fopen(filename, "r");
char line[512];
if (!fp) return -1;
while (fgets(line, sizeof(line), fp)) {
if (line[0] == '#' || line[0] == '\n') continue;
char *key = strtok(line, "=");
char *value = strtok(NULL, "\n");
if (!key || !value) continue;
if (strcmp(key, "url") == 0) strcpy(info->url, value);
else if (strcmp(key, "local_file") == 0) strcpy(info->local_file, value);
else if (strcmp(key, "downloaded") == 0) info->downloaded = atoll(value);
else if (strcmp(key, "total_size") == 0) info->total_size = atoll(value);
else if (strcmp(key, "last_update") == 0) info->last_update = atol(value);
else if (strcmp(key, "etag") == 0) strcpy(info->etag, value);
else if (strcmp(key, "last_modified") == 0) strcpy(info->last_modified, value);
}
fclose(fp);
return 0;
}
五、通过路由设备与其他 IP 通信
5.1 NAT 穿透与端口转发架构
【嵌入式设备通过路由器与外网通信架构】 ┌─────────────────────────────────────────────────────────────────┐ │ 外网客户端 │ │ (公网 IP: 203.0.113.50) │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────┐ │ 互联网 │ └─────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 家庭/企业路由器 │ │ (公网 IP: 122.121.100.200) │ │ (内网 IP: 192.168.1.1) │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ NAT 表 │ │ │ │ ┌─────────────┬─────────────┬─────────────────────────┐ │ │ │ │ │ 外部端口 │ 内部 IP │ 内部端口 │ │ │ │ │ ├─────────────┼─────────────┼─────────────────────────┤ │ │ │ │ │ 8080 → │ 192.168.1.100│ 80 (HTTP 服务) │ │ │ │ │ │ 8081 → │ 192.168.1.100│ 8081 (上传服务) │ │ │ │ │ │ 2222 → │ 192.168.1.100│ 22 (SSH) │ │ │ │ │ └─────────────┴─────────────┴─────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 嵌入式设备 │ │ (内网 IP: 192.168.1.100) │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ HTTP 服务端 (端口 80) │ │ │ │ HTTP 上传服务 (端口 8081) │ │ │ │ SSH 服务 (端口 22) │ │ │ └───────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘
5.2 UPnP 自动端口映射
/**
* upnp_port_forward.c - 使用 UPnP 自动添加端口映射
* 编译: gcc -o upnp_port_forward upnp_port_forward.c -lminiupnpc
*/
#include <stdio.h>
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
/**
* 通过 UPnP 添加端口映射
* @param external_port 外部端口
* @param internal_port 内部端口
* @param protocol 协议 (TCP/UDP)
* @param description 描述
* @return 0 成功,-1 失败
*/
int add_upnp_port_mapping(int external_port, int internal_port,
const char *protocol, const char *description)
{
struct UPNPDev *devlist = NULL;
struct UPNPUrls urls;
struct IGDdatas data;
char lanaddr[64] = {0};
int ret;
/* 1. 发现 UPnP 设备 */
devlist = upnpDiscover(2000, NULL, NULL, 0, 0, 2, NULL);
if (!devlist) {
fprintf(stderr, "未发现 UPnP 设备\n");
return -1;
}
/* 2. 获取 IGD 服务 URL */
ret = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
if (ret != 1) {
fprintf(stderr, "未找到有效的 IGD\n");
freeUPNPDevlist(devlist);
return -1;
}
printf("IGD 发现: %s\n", urls.controlURL);
printf("本地 IP: %s\n", lanaddr);
/* 3. 添加端口映射 */
ret = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
(char *)protocol, external_port, internal_port,
lanaddr, (char *)description, NULL, NULL);
if (ret != 0) {
fprintf(stderr, "端口映射失败\n");
return -1;
}
printf("端口映射成功: %d -> %s:%d (%s)\n",
external_port, lanaddr, internal_port, protocol);
/* 4. 清理 */
FreeUPNPUrls(&urls);
freeUPNPDevlist(devlist);
return 0;
}
/* 删除端口映射 */
int remove_upnp_port_mapping(int external_port, const char *protocol)
{
struct UPNPDev *devlist = NULL;
struct UPNPUrls urls;
struct IGDdatas data;
char lanaddr[64];
int ret;
devlist = upnpDiscover(2000, NULL, NULL, 0, 0, 2, NULL);
if (!devlist) return -1;
ret = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
if (ret != 1) {
freeUPNPDevlist(devlist);
return -1;
}
ret = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype,
(char *)protocol, external_port, NULL);
FreeUPNPUrls(&urls);
freeUPNPDevlist(devlist);
return ret;
}
int main()
{
/* 添加 HTTP 端口映射: 外网 8080 -> 内网 80 */
add_upnp_port_mapping(8080, 80, "TCP", "HTTP Server");
/* 添加上传服务映射: 外网 8081 -> 内网 8081 */
add_upnp_port_mapping(8081, 8081, "TCP", "Upload Service");
/* 添加 SSH 映射: 外网 2222 -> 内网 22 */
add_upnp_port_mapping(2222, 22, "TCP", "SSH Remote Access");
printf("按回车键删除映射...\n");
getchar();
remove_upnp_port_mapping(8080, "TCP");
remove_upnp_port_mapping(8081, "TCP");
remove_upnp_port_mapping(2222, "TCP");
return 0;
}
5.3 通过路由的 TCP 穿透 (打洞)
/**
* tcp_hole_punching.c - TCP 打洞技术
* 用于两个内网设备通过路由器直接通信
*/
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
/* 打洞服务器地址 */
#define STUN_SERVER_IP "123.123.123.100"
#define STUN_SERVER_PORT 3478
/* 获取 NAT 映射的外部地址 */
int get_external_address(int *external_port, char *external_ip)
{
int sock;
struct sockaddr_in stun_addr, peer_addr;
char buf[64];
socklen_t len = sizeof(peer_addr);
/* 连接 STUN 服务器 */
sock = socket(AF_INET, SOCK_DGRAM, 0);
stun_addr.sin_family = AF_INET;
stun_addr.sin_port = htons(STUN_SERVER_PORT);
inet_aton(STUN_SERVER_IP, &stun_addr.sin_addr);
/* 发送 STUN Binding Request */
sendto(sock, "STUN", 4, 0, (struct sockaddr*)&stun_addr, sizeof(stun_addr));
/* 接收响应,获取外部地址 */
recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&peer_addr, &len);
*external_port = ntohs(peer_addr.sin_port);
inet_ntop(AF_INET, &peer_addr.sin_addr, external_ip, 16);
close(sock);
return 0;
}
/* 向对端设备发起 TCP 打洞连接 */
int tcp_hole_punch(const char *peer_ip, int peer_port, int local_port)
{
int sock;
struct sockaddr_in addr;
int ret;
/* 创建 socket */
sock = socket(AF_INET, SOCK_STREAM, 0);
/* 绑定本地端口 */
addr.sin_family = AF_INET;
addr.sin_port = htons(local_port);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
/* 并发连接对端 (打洞核心) */
addr.sin_port = htons(peer_port);
inet_aton(peer_ip, &addr.sin_addr);
/* 非阻塞 connect */
fcntl(sock, F_SETFL, O_NONBLOCK);
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
/* 等待连接成功或失败 (打洞) */
usleep(100000); /* 等待 100ms */
/* 尝试 accept (作为服务器) */
/* ... */
return sock;
}
5.4 路由器配置脚本
#!/bin/bash # router_config.sh - 路由器端口转发配置 (通过 SSH 配置 OpenWrt) # 配置信息 ROUTER_IP="192.168.1.1" ROUTER_USER="root" ROUTER_PASS="password" DEVICE_IP="192.168.1.100" # 方法1: SSH 登录路由器配置 iptables ssh $ROUTER_USER@$ROUTER_IP << EOF # 添加端口转发规则 iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination $DEVICE_IP:80 iptables -t nat -A POSTROUTING -p tcp -d $DEVICE_IP --dport 80 -j MASQUERADE # 保存配置 iptables-save > /etc/iptables.rules EOF # 方法2: 通过 UCI 配置 OpenWrt ssh $ROUTER_USER@$ROUTER_IP << EOF # 添加端口转发 (OpenWrt UCI) uci add firewall redirect uci set firewall.@redirect[-1].name="HTTP_Upload" uci set firewall.@redirect[-1].src="wan" uci set firewall.@redirect[-1].proto="tcp" uci set firewall.@redirect[-1].src_dport="8080" uci set firewall.@redirect[-1].dest_ip="$DEVICE_IP" uci set firewall.@redirect[-1].dest_port="80" uci commit firewall /etc/init.d/firewall restart EOF
5.5 通信模式选择
【嵌入式设备与其他 IP 通信模式对比】 ┌─────────────────┬─────────────────────────────────────────────────────┐ │ 模式 │ 说明 │ ├─────────────────┼─────────────────────────────────────────────────────┤ │ 端口映射 │ 路由器手动配置 NAT,外网直接访问内网设备 │ │ │ 适用于需要被外部访问的场景 (Web 服务、API) │ ├─────────────────┼─────────────────────────────────────────────────────┤ │ UPnP 自动映射 │ 设备自动请求路由器开放端口,无需手动配置 │ │ │ 适用于 P2P、游戏、多媒体服务 │ ├─────────────────┼─────────────────────────────────────────────────────┤ │ 反向连接 │ 设备主动连接外网服务器 (WebSocket/HTTP) │ │ │ 最稳定,无需路由器配置,适用于数据上报场景 │ ├─────────────────┼─────────────────────────────────────────────────────┤ │ TCP 打洞 │ 通过 STUN 服务器获取公网地址,两设备直接 P2P 通信 │ │ │ 适用于需要低延迟、高带宽的场景 │ ├─────────────────┼─────────────────────────────────────────────────────┤ │ 内网直连 │ 同一局域网内通过内网 IP 直接通信 │ │ │ 最简单、最快,不需要路由器配置 │ ├─────────────────┼─────────────────────────────────────────────────────┤ │ VPN/ZeroTier │ 虚拟组网,设备如同在同一局域网 │ │ │ 适用于跨公网安全通信 │ └─────────────────┴─────────────────────────────────────────────────────┘
5.6 反向连接服务器端示例
/**
* reverse_connect_server.c - 反向连接服务端 (部署在公网)
* 嵌入式设备主动连接此服务,然后服务端可以下发指令
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#define MAX_CLIENTS 100
typedef struct {
int fd;
char ip[16];
char device_id[32];
int online;
} client_t;
client_t clients[MAX_CLIENTS];
pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER;
/* 处理设备连接 */
void *handle_device(void *arg)
{
int client_fd = *(int *)arg;
char buffer[4096];
char device_id[32] = {0};
int idx = -1;
/* 接收设备 ID */
recv(client_fd, device_id, sizeof(device_id), 0);
device_id[31] = '\0';
/* 注册设备 */
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++) {
if (!clients[i].online) {
clients[i].fd = client_fd;
strcpy(clients[i].device_id, device_id);
clients[i].online = 1;
idx = i;
break;
}
}
pthread_mutex_unlock(&clients_mutex);
printf("设备 %s 已连接 (fd=%d)\n", device_id, client_fd);
/* 循环接收数据 */
while (1) {
ssize_t n = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (n <= 0) break;
buffer[n] = '\0';
printf("[%s] %s\n", device_id, buffer);
/* 处理设备上报的数据 */
if (strncmp(buffer, "FILE_UPLOAD", 11) == 0) {
/* 处理文件上传 */
}
}
/* 设备断开 */
pthread_mutex_lock(&clients_mutex);
clients[idx].online = 0;
pthread_mutex_unlock(&clients_mutex);
close(client_fd);
printf("设备 %s 断开\n", device_id);
return NULL;
}
/* 向指定设备发送命令 */
int send_to_device(const char *device_id, const char *cmd)
{
pthread_mutex_lock(&clients_mutex);
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].online && strcmp(clients[i].device_id, device_id) == 0) {
send(clients[i].fd, cmd, strlen(cmd), 0);
pthread_mutex_unlock(&clients_mutex);
return 0;
}
}
pthread_mutex_unlock(&clients_mutex);
return -1;
}
int main()
{
int server_fd;
struct sockaddr_in addr;
/* 创建服务端 socket */
server_fd = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, 100);
printf("反向连接服务端启动,端口: 9000\n");
while (1) {
int client_fd = accept(server_fd, NULL, NULL);
pthread_t tid;
pthread_create(&tid, NULL, handle_device, &client_fd);
pthread_detach(tid);
}
return 0;
}
六、总结
| 模块 | 关键点 | 文件/工具 |
|---|---|---|
| 库部署 | libcurl + libmicrohttpd + OpenSSL | 交叉编译脚本 |
| 上传流程 | curl_easy_perform + read_callback | http_upload.c |
| 下载流程 | Range 头 + 206 响应 | http_download.c |
| 断点续传 | resume 文件 + Range 头 | multi_thread_download.c |
| 端口映射 | UPnP + iptables | upnp_port_forward.c |
| 反向连接 | 设备主动连接公网服务端 | reverse_connect_server.c |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)