Day36 TCP客户端编程 HTTP协议解析 获取实时天气信息
day36 TCP客户端编程 HTTP协议解析 获取实时天气信息
一、项目目标
使用 Wireshark 抓包工具 捕获访问 www.nowapi.com
(实际为 api.k780.com
)获取实时天气数据的 HTTP 报文,
基于抓包结果,编写一个 TCP 客户端程序(C语言),实现以下功能:
- 与远程服务器建立 TCP 连接;
- 发送符合规范的 HTTP 请求报文;
- 接收服务器响应;
- 解析 JSON 格式的天气数据;
- 提取关键字段并格式化输出为指定样式。
最终输出示例如下:
2025-09-05:星期五:台北:31℃/25℃:晴转多云
二、前置知识准备
1. 网络通信流程(TCP + HTTP)
- 使用 TCP 协议连接目标服务器(IP + 端口)
- 手动构造 HTTP 请求报文(请求行 + 请求头)
- 发送请求后接收响应(包含状态行、响应头、响应体)
- 从响应体中提取 JSON 数据并解析
2. 关键函数说明
函数 | 作用 |
---|---|
socket() | 创建套接字 |
connect() | 建立 TCP 连接 |
send() | 发送数据 |
recv() | 接收数据 |
close() | 关闭连接 |
strstr() | 查找子字符串 |
strchr() | 查找字符首次出现位置 |
inet_addr() | 将点分十进制 IP 转换为网络字节序 |
htons() | 主机字节序转网络字节序(用于端口) |
三、API 接口信息分析
目标网址
http://api.k780.com/?app=weather.today&cityNm=台北&appkey=77384&sign=5ac63f91d88ad9c5e08f6e513552b3f1&format=json
Remote Address(远程地址)
8.129.233.227:80
说明:该服务运行在标准 HTTP 端口 80 上,使用明文传输。
四、HTTP 请求报文结构(通过 Wireshark 抓取)
以下是浏览器访问上述 URL 时发出的完整 HTTP 请求报文:
GET /?app=weather.today&cityNm=台北&appkey=77384&sign=5ac63f91d88ad9c5e08f6e513552b3f1&format=json HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Connection: keep-alive
Host: api.k780.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36
注意:我们只需保留必要的字段即可完成请求。本程序中简化为以下关键字段。
五、API 响应示例(JSON 格式)
{"success": "1","result": {"weaid": "360","days": "2025-09-05","week": "星期五","cityno": "taibeixian","citynm": "台北","cityid": "101340101","temperature": "31℃/25℃","temperature_curr": "31℃","humidity": "67%","aqi": "77","weather": "晴转多云","weather_curr": "晴","weather_icon": "http://api.k780.com/upload/weather/d/0.gif","weather_icon1": "","wind": "无持续风向","winp": "1级","temp_high": "31","temp_low": "25","temp_curr": "31","humi_high": "0","humi_low": "0","weatid": "1","weatid1": "","windid": "0","winpid": "1","weather_iconid": "0"}
}
我们需要从中提取:
days
: 日期week
: 星期citynm
: 城市名temperature
: 温度范围weather
: 天气描述
六、C语言客户端实现(cli.c)
严格按照原始代码逻辑整理,仅添加详细注释,不修改变量名、函数名、结构。
#include <arpa/inet.h> // 提供IP地址转换函数(如inet_addr)
#include <fcntl.h> // 文件控制相关函数(本程序未使用)
#include <netinet/in.h> // 定义网络地址结构(如sockaddr_in)
#include <netinet/ip.h> // IP协议相关定义(本程序未实际使用)
#include <stdio.h> // 标准输入输出函数
#include <stdlib.h> // 标准库函数
#include <string.h> // 字符串处理函数(如strstr、strchr)
#include <sys/socket.h> // 套接字相关函数(如socket、connect)
#include <sys/types.h> // 基本系统数据类型
#include <time.h> // 时间相关函数(本程序未使用)
#include <unistd.h> // 系统调用函数(如close、send)// 类型别名定义:将struct sockaddr*简化为SA,方便后续使用
typedef struct sockaddr *(SA);// 向服务器发送HTTP请求命令
// 参数:conn为已建立连接的套接字描述符
int send_cmd(int conn)
{// 定义HTTP请求各部分的字符串数组char *args[7] = {NULL};// 请求行:GET方法 + 查询参数 + HTTP版本args[0] = "GET /?app=weather.today&cityNm=台北&appkey=77384&sign=5ac63f91d88ad9c5e08f6e513552b3f1&format=json HTTP/1.1\r\n";// Host头部:必须字段,指定主机域名args[1] = "Host: api.k780.com\r\n";// User-Agent:模拟Chrome浏览器请求args[2] = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)\r\n";// Accept:客户端可接受的内容类型args[3] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n";// Accept-Language:语言偏好args[4] = "Accept-Language: zh-CN,zh;q=0.9\r\n";// Accept-Encoding:支持的内容编码(压缩)args[5] = "Accept-Encoding: gzip, deflate\r\n";// Connection:保持连接;结尾两个\r\n表示头部结束args[6] = "Connection: keep-alive\r\n\r\n";// 循环发送HTTP请求的各个部分int i = 0;for (i = 0; i < 7; i++){// send(套接字, 数据, 长度, 标志位)send(conn, args[i], strlen(args[i]), 0);}// 注意:原函数无返回值,但建议可加返回值表示成功
}int main(int argc, char **argv)
{// 创建TCP套接字:AF_INET(IPv4协议),SOCK_STREAM(TCP类型),0(默认协议)int conn = socket(AF_INET, SOCK_STREAM, 0);if (-1 == conn) // 检查套接字创建是否失败{perror("socket"); // 输出错误信息return 1; // 异常退出}// 定义并初始化服务器地址结构struct sockaddr_in ser;bzero(&ser, sizeof(ser)); // 将地址结构清零// 设置服务器地址信息ser.sin_family = AF_INET; // 使用IPv4协议ser.sin_port = htons(80); // 设置端口为80(HTTP默认端口),htons转换字节序ser.sin_addr.s_addr = inet_addr("8.129.233.227"); // 设置服务器IP地址为8.129.233.227(天气API服务器)// 与服务器建立TCP连接int ret = connect(conn, (SA)&ser, sizeof(ser));if (-1 == ret) // 检查连接是否失败{perror("connect error\n"); // 输出错误信息return 1; // 异常退出}// 发送HTTP请求send_cmd(conn);// 接收服务器响应char buf[1024] = {0}; // 存储响应数据的缓冲区ret = recv(conn, buf, sizeof(buf), 0); // 接收数据if (ret <= 0) // 检查接收是否失败{perror("recv"); // 输出错误信息return 1; // 异常退出}// 定义响应成功的标志char *flag1 = "HTTP/1.1 200 OK"; // HTTP响应状态码:成功char *flag2 = "\"success\":\"1\""; // API返回成功标识// 检查响应是否成功(同时包含HTTP 200和API success=1)if (strstr(buf, flag1) && strstr(buf, flag2)){// 定位各个字段在响应中的位置(链式查找,基于前一个字段的位置)char *days = strstr(buf, "days"); // 查找days字段char *week = strstr(days, "week"); // 在days之后查找week字段char *citynm = strstr(week, "citynm"); // 在week之后查找citynm字段char *temperature = strstr(citynm, "temperature"); // 查找temperature字段char *weather = strstr(temperature, "weather"); // 查找weather字段char *end = NULL; // 用于标记字段值的结束位置// 提取days字段值days += 7; // 跳过"days":"(7个字符)end = strchr(days, '"'); // 查找结束双引号*end = '\0'; // 替换为字符串结束符// 提取week字段值week += 7; // 跳过"week":"(7个字符)end = strchr(week, '"');*end = '\0';// 提取citynm字段值citynm += 9; // 跳过"citynm":"(9个字符)end = strchr(citynm, '"');*end = '\0';// 提取temperature字段值temperature += 14; // 跳过"temperature":"(14个字符)end = strchr(temperature, '"');*end = '\0';// 提取weather字段值weather += 10; // 跳过"weather":"(10个字符)end = strchr(weather, '"');*end = '\0';// 打印解析后的天气信息(按要求格式输出)printf("%s:%s:%s:%s:%s\n", days, week, citynm, temperature, weather);}close(conn); // 关闭套接字,释放连接资源return 0; // 程序正常退出
}
七、程序运行理想结果
假设网络正常、API 可用,运行程序后应输出如下内容:
2025-09-05:星期五:台北:31℃/25℃:晴转多云
注:实际输出取决于 API 当前返回的数据,若城市或时间变化,输出也会相应更新。
八、关键点总结
模块 | 要点 |
---|---|
TCP连接 | 使用 socket() + connect() 建立与 8.129.233.227:80 的连接 |
HTTP请求构造 | 手动拼接请求行和请求头,注意 \r\n 结尾与双换行结束头部 |
数据接收 | 使用 recv() 接收响应,注意缓冲区大小 |
响应解析 | 使用 strstr() 定位字段,strchr() 截取值,手动解析 JSON(简易方式) |
安全性 | 未处理分包、粘包、编码压缩等问题,仅适用于简单场景 |
扩展建议 | 可引入 JSON 解析库(如 cJSON)提升健壮性 |
九、Wireshark 抓包提示(图片注释保留)
【图片注释:使用 Wireshark 捕获访问 api.k780.com 时的 TCP 与 HTTP 数据包,观察三次握手、HTTP 请求/响应、四次挥手全过程】
- 过滤条件可输入:
ip.addr == 8.129.233.227 && tcp.port == 80
- 观察 TCP 三次握手(SYN, SYN-ACK, ACK)
- 查看 HTTP GET 请求内容
- 分析服务器返回的 JSON 响应体
- 注意 Content-Length 与实际数据长度是否一致
十、注意事项
- 本程序为简化版,未处理
gzip
压缩内容(服务器可能返回压缩数据) - 若服务器启用 HTTPS(端口443),需使用 OpenSSL 库进行加密通信
- 实际开发中建议使用
libcurl
等成熟库替代手动 TCP 编程 - JSON 解析建议使用专业库避免错误(如字段顺序变动导致
strstr
失效)
✅ 本日所学知识点涵盖:
- TCP 客户端编程
- HTTP 协议原理与报文构造
- 字符串处理(
strstr
,strchr
) - 结构化数据提取(简易 JSON 解析)
- 网络调试工具使用(Wireshark)
- 系统调用接口应用(socket, connect, send, recv, close)