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)