目录
[1. TCP 网络通信](#1. TCP 网络通信)
[2. HTTP 协议](#2. HTTP 协议)
[3. JSON 解析](#3. JSON 解析)
[1. 核心函数说明](#1. 核心函数说明)
[2. 关键代码片段](#2. 关键代码片段)
[(1)TCP 连接建立](#(1)TCP 连接建立)
[(2)HTTP 请求构造(以实时天气为例)](#(2)HTTP 请求构造(以实时天气为例))
[1. 实时天气查询](#1. 实时天气查询)
[2. 未来天气查询](#2. 未来天气查询)
[3. 退出功能](#3. 退出功能)
[1. 核心问题及解决](#1. 核心问题及解决)
[2. 优化点](#2. 优化点)
一、项目原理
1. TCP 网络通信
客户端通过socket()创建套接字,connect()连接到天气 API 服务器(地址:103.205.5.206,端口:80),通过send()发送 HTTP 请求,recv()接收服务器响应数据。
2. HTTP 协议
构造符合 HTTP/1.1 规范的 GET 请求报文,包含请求行、请求头(Host、User-Agent 等),服务器返回包含 JSON 数据的 HTTP 响应,需跳过响应头提取 JSON 正文。
3. JSON 解析
使用 cJSON 库解析 JSON 字符串:
cJSON_Parse()将 JSON 字符串转为 cJSON 对象;cJSON_GetObjectItem()提取指定字段;cJSON_ArrayForEach()遍历 JSON 数组;cJSON_Delete()释放内存,避免内存泄漏。
二、代码结构
1. 核心函数说明
| 函数名 | 功能 |
|---|---|
clear_input() |
清空输入缓冲区,解决 scanf 与 fgets 的输入冲突 |
parse_now_weather() |
解析实时天气 JSON 数据,格式化输出地区、温度、湿度等信息 |
parse_future_weather() |
解析未来天气 JSON 数据,拆分显示最低 / 最高温度 |
now_weather() |
构造实时天气 HTTP 请求,发送并接收数据,调用解析函数 |
future_weather() |
构造未来天气 HTTP 请求,发送并接收数据,调用解析函数 |
print_menu() |
打印操作菜单,提供实时 / 未来天气查询、退出选项 |
main() |
主函数,创建套接字、连接服务器、循环处理用户指令 |
2. 关键代码片段
(1)TCP 连接建立
cpp
fd = socket(AF_INET,SOCK_STREAM,0); // 创建TCP套接字
struct sockaddr_in addr;
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("103.205.5.206"); // API服务器地址
addr.sin_port = htons(80); // HTTP默认端口
connect(fd,(const struct sockaddr*)&addr,sizeof(addr)); // 连接服务器
(2)HTTP 请求构造(以实时天气为例)
cpp
sprintf(sbuf,"GET /?app=weather.realtime&weaid=%s&appkey=78726&sign=3a82de6ed4f0bfc8c8064cc51875a103&format=json HTTP/1.1\r\n",city);
char *args[8] = {sbuf, "Host: api.k780.com\r\n", ..., "Upgrade-Insecure-Requests: 1\r\n\r\n"};
for(i=0;i<8;++i) send(fd,args[i],strlen(args[i]),0); // 发送请求
(3)未来天气温度拆分解析
cpp
const char *temp_low = cJSON_GetObjectItem(item, "temp_low") ? cJSON_GetObjectItem(item, "temp_low")->valuestring : "未知";
const char *temp_high = cJSON_GetObjectItem(item, "temp_high") ? cJSON_GetObjectItem(item, "temp_high")->valuestring : "未知";
printf("最低温度:%s℃\n", temp_low);
printf("最高温度:%s℃\n", temp_high);
三、项目步骤
- 环境准备 :安装 cJSON 库(
sudo apt install libcjson-dev); - 代码编译 :
gcc weather.c -o weather -lcjson -lm; - 运行程序 :
./weather; - 功能测试 :
- 输入
1,输入城市拼音(如 xian),查看实时天气; - 输入
2,输入城市拼音,查看未来天气(含高低温); - 输入
3,退出程序。
- 输入
4、项目结果
1. 实时天气查询
cpp
weather 天气查询
1.实时天气
2.未来天气
3.退出
请输入操作指令:1
input a city:xian
========== 实时天气 ==========
地区:陕西-西安
星期:星期四
天气:多云
温度:12℃
湿度:30%
风向:东北风
AQI:56
==============================
2. 未来天气查询
cpp
weather 天气查询
1.实时天气
2.未来天气
3.退出
请输入操作指令:2
input a city:xian
========== 未来天气 ==========
日期:2026-03-12(星期四)
天气:多云
最低温度:8℃
最高温度:15℃
------------------------------
日期:2026-03-13(星期五)
天气:晴
最低温度:9℃
最高温度:16℃
------------------------------
3. 退出功能
cpp
请输入操作指令:3
天气查询已退出
五、项目分析与问题解决
1. 核心问题及解决
- 输入冲突问题 :scanf 读取指令后,输入缓冲区残留换行符,导致 fgets 读取城市名时直接为空,通过
clear_input()清空缓冲区解决; - JSON 解析失败 :未来天气 API 返回的 JSON 结构为
result->future数组,原逻辑误将result当作数组,修正为先取result对象,再遍历future数组; - 温度显示需求 :按要求拆分未来天气温度为最低 / 最高温,提取
temp_low和temp_high字段替换原temp字段; - 数据接收不完整:增大缓冲区至 8192 字节,确保能接收完整的 HTTP 响应数据。
2. 优化点
- 增加重连错误校验,避免套接字关闭后重连失败导致程序崩溃;
- 兼容 JSON 起始位置,通过
strstr(response, "{")兜底查找 JSON 正文; - 字段提取时增加空指针判断,避免空指针访问导致程序崩溃。
六、完整代码
cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <strings.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include "cJSON.h"
int fd;
char sbuf[1024];//实时天气
char fbuf[1024];//未来天气
// 解决scanf和fgets的输入冲突
void clear_input()
{
while (getchar() != '\n');
}
// 解析实时天气JSON(适配截图中的返回格式)
void parse_now_weather(const char *response)
{
// 跳过HTTP响应头,定位JSON起始(找到第一个{)
const char *json_start = strstr(response, "{\"success\"");
if (!json_start)
{
json_start = strstr(response, "{"); // 兼容其他JSON起始
}
if (!json_start)
{
printf("未找到天气数据\n");
return;
}
// 解析JSON
cJSON *root = cJSON_Parse(json_start);
if (!root)
{
printf("JSON解析失败: %s\n", cJSON_GetErrorPtr());
return;
}
// 提取核心字段(完全适配截图中的返回结构)
cJSON *result = cJSON_GetObjectItem(root, "result");
if (result && cJSON_IsObject(result))
{
// 基础城市信息
const char *area1 = cJSON_GetObjectItem(result, "area_1") ? cJSON_GetObjectItem(result, "area_1")->valuestring : "未知";
const char *area2 = cJSON_GetObjectItem(result, "area_2") ? cJSON_GetObjectItem(result, "area_2")->valuestring : "未知";
// 实时天气详情
cJSON *realTime = cJSON_GetObjectItem(result, "realTime");
if (realTime && cJSON_IsObject(realTime))
{
const char *week = cJSON_GetObjectItem(realTime, "week") ? cJSON_GetObjectItem(realTime, "week")->valuestring : "未知";
const char *wtNm = cJSON_GetObjectItem(realTime, "wtNm") ? cJSON_GetObjectItem(realTime, "wtNm")->valuestring : "未知";
const char *wtTemp = cJSON_GetObjectItem(realTime, "wtTemp") ? cJSON_GetObjectItem(realTime, "wtTemp")->valuestring : "未知";
const char *wtHumi = cJSON_GetObjectItem(realTime, "wtHumi") ? cJSON_GetObjectItem(realTime, "wtHumi")->valuestring : "未知";
const char *wtWindNm = cJSON_GetObjectItem(realTime, "wtWindNm") ? cJSON_GetObjectItem(realTime, "wtWindNm")->valuestring : "未知";
const char *wtAqi = cJSON_GetObjectItem(realTime, "wtAqi") ? cJSON_GetObjectItem(realTime, "wtAqi")->valuestring : "未知";
// 格式化输出(仅显示关键信息,隐藏冗余字段)
printf("\n========== 实时天气 ==========\n");
printf("地区:%s-%s\n", area1, area2);
printf("星期:%s\n", week);
printf("天气:%s\n", wtNm);
printf("温度:%s℃\n", wtTemp);
printf("湿度:%s%%\n", wtHumi);
printf("风向:%s\n", wtWindNm);
printf("AQI:%s\n", wtAqi);
printf("==============================\n");
}
}
cJSON_Delete(root);
}
// 解析未来天气JSON(适配API返回格式)
void parse_future_weather(const char *response)
{
const char *json_start = strstr(response, "{\"success\"");
if (!json_start)
{
json_start = strstr(response, "{");
}
if (!json_start)
{
printf("未找到天气数据\n");
return;
}
cJSON *root = cJSON_Parse(json_start);
if (!root)
{
printf("JSON解析失败: %s\n", cJSON_GetErrorPtr());
return;
}
cJSON *result = cJSON_GetObjectItem(root, "result");
if (result && cJSON_IsArray(result))
{
printf("\n========== 未来天气 ==========\n");
cJSON *item = NULL;
cJSON_ArrayForEach(item, result)
{
const char *days = cJSON_GetObjectItem(item, "days") ? cJSON_GetObjectItem(item, "days")->valuestring : "未知";
const char *week = cJSON_GetObjectItem(item, "week") ? cJSON_GetObjectItem(item, "week")->valuestring : "未知";
const char *weather = cJSON_GetObjectItem(item, "weather") ? cJSON_GetObjectItem(item, "weather")->valuestring : "未知";
//const char *temp = cJSON_GetObjectItem(item, "temp") ? cJSON_GetObjectItem(item, "temp")->valuestring : "未知";
const char *temp_low = cJSON_GetObjectItem(item, "temp_low") ? cJSON_GetObjectItem(item, "temp_low")->valuestring : "未知";
const char *temp_high = cJSON_GetObjectItem(item, "temp_high") ? cJSON_GetObjectItem(item, "temp_high")->valuestring : "未知";
printf("日期:%s(%s)\n", days, week);
printf("天气:%s\n", weather);
// printf("温度:%s℃\n", temp);
printf("最低温度:%s℃\n", temp_low);
printf("最高温度:%s℃\n", temp_high);
printf("------------------------------\n");
}
} else
{
printf("未找到未来天气数据\n");
}
cJSON_Delete(root);
}
// 实时天气(保留你能输入拼音的逻辑,仅替换输出为解析结果)
void now_weather()
{
char city[256];
printf("input a city:");
fgets(city,sizeof(city),stdin);
city[strlen(city)-1] = '\0';
sprintf(sbuf,"GET /?app=weather.realtime&weaid=%s&appkey=78726&sign=3a82de6ed4f0bfc8c8064cc51875a103&format=json HTTP/1.1\r\n",city);
char *args[8];
args[0] = sbuf;
args[1] = "Host: api.k780.com\r\n";
args[2] = "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n";
args[3] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n";
args[4] = "Accept-Language: en-US,en;q=0.5\r\n";
args[5] = "Accept-Encoding: gzip, deflate\r\n";
args[6] = "Connection: keep-alive\r\n";
args[7] = "Upgrade-Insecure-Requests: 1\r\n\r\n";
int i = 0;
for(i=0;i<8;++i)
{
send(fd,args[i],strlen(args[i]),0);
}
// 增大缓冲区,接收完整数据
char buf[8192]={0};
recv(fd,buf,sizeof(buf),0);
// 替换原始打印,调用解析函数
parse_now_weather(buf);
}
// 未来天气(同逻辑优化)
void future_weather()
{
char city[256];
printf("input a city:");
fgets(city,sizeof(city),stdin);
city[strlen(city)-1] = '\0';
sprintf(fbuf,"GET /?app=weather.future&weaid=%s&appkey=78726&sign=3a82de6ed4f0bfc8c8064cc51875a103&format=json HTTP/1.1\r\n",city);
char *args[8];
args[0] = fbuf;
args[1] = "Host: api.k780.com\r\n";
args[2] = "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n";
args[3] = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n";
args[4] = "Accept-Language: en-US,en;q=0.5\r\n";
args[5] = "Accept-Encoding: gzip, deflate\r\n";
args[6] = "Connection: keep-alive \r\n";
args[7] = "Upgrade-Insecure-Requests: 1\r\n\r\n";
int i = 0;
for(i=0;i<8;++i)
{
send(fd,args[i],strlen(args[i]),0);
}
char buf[8192]={0};
recv(fd,buf,sizeof(buf),0);
parse_future_weather(buf);
}
// 打印菜单(无修改)
void print_menu()
{
printf("\n weather 天气查询\n");
printf("1.实时天气\n");
printf("2.未来天气\n");
printf("3.退出\n");
printf("请输入操作指令:");
}
// 主函数(仅补充重连错误校验)
int main(int argc, const char *argv[])
{
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
perror("socket fail");
return -1;
}
struct sockaddr_in addr;
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("103.205.5.206");
addr.sin_port = htons(80);
if(connect(fd,(const struct sockaddr*)&addr,sizeof(addr)) < 0)
{
perror("connect fail");
return -1;
}
int choice;
while(1)
{
print_menu();
scanf("%d",&choice);
clear_input();
switch (choice)
{
case 1:
now_weather();
close(fd);
fd = socket(AF_INET,SOCK_STREAM,0);
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("103.205.5.206");
addr.sin_port = htons(80);
// 补充重连校验
if(connect(fd,(const struct sockaddr*)&addr,sizeof(addr)) < 0) {
perror("reconnect fail");
return -1;
}
break;
case 2:
future_weather();
close(fd);
fd = socket(AF_INET,SOCK_STREAM,0);
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("103.205.5.206");
addr.sin_port = htons(80);
if(connect(fd,(const struct sockaddr*)&addr,sizeof(addr)) < 0) {
perror("reconnect fail");
return -1;
}
break;
case 3:
close(fd);
printf("天气查询已退出\n");
exit(0);
default:
printf("指令无效,请重新输入\n");
break;
}
}
return 0;
}