Day36 TCP客户端编程 HTTP协议解析 获取实时天气信息

发布于:2025-09-06 ⋅ 阅读:(14) ⋅ 点赞:(0)

day36 TCP客户端编程 HTTP协议解析 获取实时天气信息


一、项目目标

使用 Wireshark 抓包工具 捕获访问 www.nowapi.com(实际为 api.k780.com)获取实时天气数据的 HTTP 报文,
基于抓包结果,编写一个 TCP 客户端程序(C语言),实现以下功能:

  1. 与远程服务器建立 TCP 连接;
  2. 发送符合规范的 HTTP 请求报文;
  3. 接收服务器响应;
  4. 解析 JSON 格式的天气数据;
  5. 提取关键字段并格式化输出为指定样式。

最终输出示例如下:

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)

网站公告

今日签到

点亮在社区的每一天
去签到