代码概述
这段代码实现了一个天气查询系统,支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型,并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求,并解析返回的 JSON 数据来展示天气信息。
cpp
#include "main.h"
#include "cJSON.h"
#define BUFFER_SIZE 30000
#define TEMP_BUFFER_SIZE 4096
#define COLOR_FIELD "\033[34m" // 字段蓝色
#define COLOR_VALUE "\033[36m" // 值青色
#define COLOR_WARN "\033[33m" // 警告黄色
#define COLOR_RESET "\033[0m" // 重置颜色
#define COLOR_HEAD "\033[35m" // 提示头颜色
char weather_inquiry[4][1024] =
{
{"实时天气"},
{"未来天气"},
{"历史天气"},
{"退出查询"},
};
int pos_start = 0;
int pos_end = 4;
int focus = 0;//选择的焦点
int menu_flag = 1;
char city[512];//储存城市名字
int sockfd;
int getch(void)
{
struct termios oldt, newt;
int ch;
int esc_mode = 0;
unsigned long key = 0;
//获取终端属性信息
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
//设置非阻塞模式
newt.c_lflag &= ~(ICANON | ECHO);
newt.c_cc[VMIN] = 0;//读取最小字符
newt.c_cc[VTIME] = 1;//等待时间10/1秒
//修改new中的ECHO和ICANON参数,使得new为不回显输入内容
//设置终端信息
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
//组合多字节按键
for(int i = 0;i < 3;i++)
{
ch = getchar();
if(ch == EOF)break;
key = (key << 8)|(ch & 0xFF);
}
//用完之后,恢复原来的终端属性
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
switch(key){
case 0x1B5B41: return KEY_UP;
case 0x1B5B42: return KEY_DOWN;
case 0x0A: return KEY_ENTER;
default: return (key&0xFF);//返回首个有效字符
}
}
void View_two(void)
{
if(menu_flag == 2)
{
int i = 0;
printf("\033[10;35H");
printf("| 天气查询 |\n");
printf("\033[0m");
for(i = pos_start;i <pos_end;i++)
{
if(i == focus)
{
printf("\033[%d;50H",i+12);
printf("|\033[30;43m%2d.%s\033[0m\n",i+1,weather_inquiry[i]);
}else{
printf("\033[%d;50H",12+i);
printf("|%2d.%-20s\n",i+1,weather_inquiry[i]);
}
}
}
return;
}
void city_name(void)
{
scanf("%s",city);
while(getchar()!= '\n');
printf("\033[0m");
if(strncmp(city,"quit",4) == 0)
{
exit(0);
printf("感谢使用!\n");
}
}
#if 1
void view(void)
{
if(menu_flag == 1)
{
printf("\033[15;55H");
printf("\033[;31m");
printf("输入quit退出\n");
printf("\033[10;25H");
printf("\033[;33m");
printf("~欢迎来到天气查询系统~\n");
printf("\033[0m");
printf("\033[12;25H");
printf("\033[;34m");
printf("请输入你要查询的城市:");
// 清空城市名
memset(city, 0, sizeof(city));
city_name();
menu_flag = 2;
system("clear");
}else if(menu_flag == 2)
{
View_two();
}
return;
}
#endif
void CreateTcpClient(void)//创建TCP
{
int ret = 0;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("sockfd fail");
return;
}
struct sockaddr_in seraddr;
bzero(&seraddr,sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(PORT);
seraddr.sin_addr.s_addr = inet_addr(IP);
if(connect(sockfd,(struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
{
perror("connect fail");
return;
}
}
//发送HTTP请求(参数:天气类型)
void SendHttpRequest(int sockfd,char *head)
{
char tmpbuff[4096] = {0};
int written = 0;
const char *template =
"GET %s HTTP/1.1\r\n"
"Host: api.k780.com\r\n"
"User-Agent: WeatherClient/1.0\r\n"
"Accept: */*\r\n"
"Connection: close\r\n" // 改为短连接
"\r\n"; // 必须的空行
// 安全格式化(限制最大长度)
written = snprintf(tmpbuff,sizeof(tmpbuff),template,head);
if (written >= sizeof(tmpbuff)) {
fprintf(stderr, "Request too large (max %zd bytes)\n", sizeof(tmpbuff));
return;
}
//发送请求
ssize_t sent = send(sockfd,tmpbuff,written,0);
if(sent != written)
{
perror("send fail");
return;
}
}
void RecvSendWeather(void)
{
int fd = 0;
char buff[BUFFER_SIZE] = {0}; // 确保初始化为全0
char cmpbuff[BUFFER_SIZE] = {0}; // 用于存储处理后的数据
char tmpbuff[TEMP_BUFFER_SIZE] = {0}; // 临时缓冲区
ssize_t nsize = 0; // recv函数接收的数据大小
char *ptmp = NULL; // 临时指针
char *pstart = NULL; // 数据起始位置指针
char *pend = NULL; // 数据结束位置指针
fd = open("recv.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
while(1)
{
memset(tmpbuff, 0, sizeof(tmpbuff)); // 清空临时缓冲区
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if(nsize <= 0)
{
break;
}
strncat(buff, tmpbuff, (size_t)nsize); // 将接收到的数据追加到buff中
if(strstr(tmpbuff, "0\r\n") != NULL)
break;
}
//查找数据正文的起始位置
ptmp = strstr(buff, "\r\n\r\n");
if(ptmp != NULL)
{
ptmp += 4; //跳过"\r\n\r\n"
ptmp = strstr(ptmp, "\r\n");
if(ptmp != NULL)
{
ptmp += 2;
pstart = ptmp; //标记数据起始位置
pend = strstr(ptmp, "\r\n"); //查找数据结束位置
if(pend != NULL)
{
strncat(cmpbuff, pstart, pend - pstart);
}
}
}
write(fd, cmpbuff, strlen(cmpbuff)); // 将处理后的数据写入文件
close(fd);
close(sockfd);
}
void real_time_weather()
{
int ret = -1;
int fd = -1;
char *buffer = NULL;
ssize_t bytes_read = 0;
CreateTcpClient(); // 创建新的连接
if(!(buffer = malloc(BUFFER_SIZE))){
perror("malloc fail");
return;
}
if((fd = open("recv.txt", O_RDONLY)) == -1){
perror("open fail");
return;
}
if((bytes_read = read(fd, buffer, BUFFER_SIZE)) <= 0){
fprintf(stderr, "Read failed or empty file\n");
return;
}
buffer[bytes_read] = '\0'; // 确保字符终止
cJSON *root = cJSON_Parse(buffer);
if(!root){
fprintf(stderr, "JSON parse failed\n");
return;
}
// 使用结构体定义字段映射
struct FieldMapping {
const char *json_key;
const char *display_name;
int required; // 1表示必须字段
} fields[] = {
{"days", "日期", 1},
{"week", "星期", 1},
{"citynm", "城市", 1},
{"temperature_curr", "当前温度", 1},
{"temp_high", "最高温度", 0},
{"temp_low", "最低温度", 0},
{"weather", "天气状况", 1},
{"humidity", "当前湿度", 0},
{"humi_high", "最大湿度", 0},
{"humi_low", "最小湿度", 0},
{"wind", "风向", 1},
{"winp", "风力", 1},
{"aqi", "PM2.5", 0}
};
cJSON *result = cJSON_GetObjectItem(root, "result");
if(!result){
fprintf(stderr, "Missing result object\n");
cJSON_Delete(root);
return;
}
// 遍历所有字段定义
for(size_t i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
{
cJSON *item = cJSON_GetObjectItem(result, fields[i].json_key);
printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[i].display_name);
if(item && item->valuestring)
{
printf(COLOR_VALUE"%s\n"COLOR_RESET, item->valuestring);
}
else
{
printf(COLOR_WARN"N/A\n"COLOR_RESET);
if(fields[i].required)
{
fprintf(stderr, "Missing required field: %s\n", fields[i].json_key);
cJSON_Delete(root);
return;
}
}
}
ret = 0;
free(buffer);
cJSON_Delete(root);
close(fd);
}
void future_weather()
{
int ret = -1;
int fd = -1;
char *buffer = NULL;
ssize_t bytes_read = 0;
CreateTcpClient(); // 创建新的连接
if(!(buffer = malloc(BUFFER_SIZE))){
perror("malloc fail");
return;
}
if((fd = open("recv.txt",O_RDONLY)) == -1){
perror("open fail");
return;
}
if((bytes_read = read(fd,buffer,BUFFER_SIZE)) <= 0){
fprintf(stderr, "Read failed or empty file\n");
return;
}
buffer[bytes_read] = '\0';//确保字符终止
cJSON *root = cJSON_Parse(buffer);
if(!root){
fprintf(stderr, "JSON parse failed\n");
return;
}
// 使用结构体定义字段映射
struct FieldMapping {
const char *json_key;
const char *display_name;
int required; // 1表示必须字段
} fields[] = {
{"days", "日期", 1},
{"week", "星期", 1},
{"citynm", "城市", 1},
{"temp_high", "最高温度", 0},
{"temp_low", "最低温度", 0},
{"weather", "天气状况", 1},
{"wind", "风向", 1},
{"winp", "风力", 1},
};
cJSON *result = cJSON_GetObjectItem(root,"result");
if(!result){
fprintf(stderr, "Missing result object\n");
cJSON_Delete(root);
}
int i = 0;
cJSON *item;
cJSON_ArrayForEach(item,result)
{
printf(COLOR_HEAD"=============第%d天气预报=============\n"COLOR_RESET,i+1);
// 遍历所有字段定义
for (size_t j = 0; j < sizeof(fields)/sizeof(fields[0]); j++) {
cJSON *field = cJSON_GetObjectItem(item, fields[j].json_key);
printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[j].display_name);
if (field && field->valuestring) {
printf(COLOR_VALUE"%s\n"COLOR_RESET, field->valuestring);
} else {
printf(COLOR_WARN"N/A\n"COLOR_RESET);
if (fields[j].required) {
fprintf(stderr, "Missing required field: %s\n", fields[j].json_key);
}
}
}
i++;
}
cJSON_Delete(root);
free(buffer);
close(fd);
}
void historical_weather()
{
}
void MenuChoose(void)
{
int key = getch();
char tmpbuff[1024]={0};
//ESC按键处理
if(key == KEY_ESC){
if(menu_flag == 2){
menu_flag = 1;//切换到菜单一
system("clear");
}
}
switch(key){
case KEY_UP:
if(focus > pos_start)
{
system("clear");
focus--;
}
break;
case KEY_DOWN:
if(focus < pos_end)
{
system("clear");
focus++;
}
break;
case KEY_ENTER:
switch(focus){
case 0:
system("clear");
CreateTcpClient();
sprintf(tmpbuff,"/?app=weather.today&weaid=%s&appkey=%s&sign=%s",city,API_KEY,API_SIGN);
SendHttpRequest(sockfd,tmpbuff);
RecvSendWeather();
real_time_weather();
if (sockfd > 0) {
close(sockfd);
sockfd = -1;
}
break;
case 1:
system("clear");
CreateTcpClient();
sprintf(tmpbuff,"http://api.k780.com/?app=weather.future&weaid=%s&appkey=%s&sign=%s&format=json",city,API_KEY,API_SIGN);
SendHttpRequest(sockfd,tmpbuff);
RecvSendWeather();
future_weather();
break;
case 2:
break;
case 3:
printf("感谢使用!\n");
exit(0);
break;
}
}
}
int main(int argc, const char *argv[])
{
system("clear");
while(1)
{
view();
MenuChoose();
}
return 0;
}
演示视频
天气查询演示视频
一、函数总览
全局变量
-
weather_inquiry[4][1024]
:存储菜单选项,包括实时天气、未来天气、历史天气和退出查询。 -
pos_start
和pos_end
:定义菜单显示的起始和结束位置。 -
focus
:当前用户选择的菜单焦点。 -
menu_flag
:标志当前显示的菜单层级,1
表示主菜单,2
表示二级菜单。 -
city[512]
:存储用户输入的城市名称。 -
sockfd
:TCP 套接字文件描述符。
第二部分:输入处理和菜单显示
getch()
函数
-
功能:捕捉用户按键输入,支持方向键和回车键。
-
实现:
-
使用
termios
修改终端属性,禁用回显和行缓冲。 -
捕捉按键组合,识别上方向键(
KEY_UP
)、下方向键(KEY_DOWN
)和回车键(KEY_ENTER
)。 -
恢复终端属性。
-
View_two()
函数
-
功能:显示二级菜单,包括实时天气、未来天气、历史天气和退出查询。
-
实现:
-
根据
focus
的值,高亮显示当前选中的菜单项。 -
使用终端控制字符(如
\033
)设置文字颜色和位置。
-
city_name()
函数
-
功能:获取用户输入的城市名称,并检查是否为退出命令。
-
实现:
-
使用
scanf
获取输入,并清除输入缓冲区。 -
如果输入为
quit
,程序退出。
-
view()
函数
-
功能 :根据
menu_flag
的值,显示主菜单或二级菜单。 -
实现:
-
主菜单显示欢迎信息,并提示用户输入城市名称。
-
二级菜单显示天气查询选项。
-
第三部分:TCP 连接和 HTTP 请求
CreateTcpClient()
函数
-
功能:创建一个 TCP 套接字,并连接到指定的服务器。
-
实现:
-
使用
socket
创建套接字。 -
设置服务器地址和端口。
-
使用
connect
建立连接。
-
SendHttpRequest()
函数
-
功能:发送 HTTP GET 请求。
-
实现:
-
使用
snprintf
格式化请求头,包括目标 URL 和必要头部信息。 -
使用
send
发送请求。
-
第四部分:数据接收和处理
RecvSendWeather()
函数
-
功能:接收服务器响应,并提取数据正文。
-
实现:
-
使用
recv
循环接收数据,直到遇到空行(0\r\n
)。 -
查找数据正文的起始和结束位置,提取并存储到文件中。
-
第五部分:实时天气查询
real_time_weather()
函数
-
功能:解析实时天气 JSON 数据,并展示相关信息。
-
实现:
-
从文件读取 JSON 数据。
-
使用
cJSON
解析 JSON 数据。 -
定义字段映射结构体,遍历字段并显示对应值。
-
检查必填字段是否存在,若缺失则报错。
-
第六部分:未来天气查询
future_weather()
函数
-
功能:解析未来天气 JSON 数据,并展示相关信息。
-
实现:
-
从文件读取 JSON 数据。
-
使用
cJSON
解析 JSON 数据。 -
定义字段映射结构体,遍历字段并显示对应值。
-
遍历 JSON 数组,逐条显示未来天气信息。
-
第七部分:菜单选择和主程序
MenuChoose()
函数
-
功能:处理用户按键输入,实现菜单导航和功能选择。
-
实现:
-
根据按键调整
focus
值,实现菜单上下导航。 -
处理回车键,根据选中项执行对应功能:
-
实时天气查询:构造 URL 并发送请求,调用
real_time_weather
。 -
未来天气查询:构造 URL 并发送请求,调用
future_weather
。 -
退出程序:退出并显示感谢信息。
-
-
main()
函数
-
功能:主程序入口,循环显示菜单并处理用户输入。
-
实现:
-
清屏并初始化终端。
-
循环调用
view
和MenuChoose
,实现菜单显示和交互。
-
二、重点函数
RecvSendWeather()
函数
cpp
void RecvSendWeather(void)
{
int fd = 0;
char buff[BUFFER_SIZE] = {0}; // 确保初始化为全0
char cmpbuff[BUFFER_SIZE] = {0}; // 用于存储处理后的数据
char tmpbuff[TEMP_BUFFER_SIZE] = {0}; // 临时缓冲区
ssize_t nsize = 0; // recv函数接收的数据大小
char *ptmp = NULL; // 临时指针
char *pstart = NULL; // 数据起始位置指针
char *pend = NULL; // 数据结束位置指针
fd = open("recv.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);
while(1)
{
memset(tmpbuff, 0, sizeof(tmpbuff)); // 清空临时缓冲区
nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);
if(nsize <= 0)
{
break;
}
strncat(buff, tmpbuff, (size_t)nsize); // 将接收到的数据追加到buff中
if(strstr(tmpbuff, "0\r\n") != NULL)
break;
}
//查找数据正文的起始位置
ptmp = strstr(buff, "\r\n\r\n");
if(ptmp != NULL)
{
ptmp += 4; //跳过"\r\n\r\n"
ptmp = strstr(ptmp, "\r\n");
if(ptmp != NULL)
{
ptmp += 2;
pstart = ptmp; //标记数据起始位置
pend = strstr(ptmp, "\r\n"); //查找数据结束位置
if(pend != NULL)
{
strncat(cmpbuff, pstart, pend - pstart);
}
}
}
write(fd, cmpbuff, strlen(cmpbuff)); // 将处理后的数据写入文件
close(fd);
close(sockfd);
}
1. 初始化与文件准备
- 定义缓冲区
buff
(存储完整数据)、cmpbuff
(存储处理后的数据)和tmpbuff
(临时接收数据)。 - 创建/清空文件
recv.txt
用于保存最终数据。
2. 循环接收数据
- 通过
recv
从套接字sockfd
接收数据到tmpbuff
。 - 将每次接收的数据追加到主缓冲区
buff
中。 - 终止条件 :当收到包含
"0\r\n"
的数据时,认为传输结束,退出循环。
3. 数据解析
- 定位数据起始点 :
- 查找首个
\r\n\r\n
,跳过HTTP头部。 - 继续查找下一个
\r\n
,跳过可能的分块长度字段(如5\r\n
)。
- 查找首个
- 提取数据正文 :
- 从
pstart
到下一个\r\n
之间的内容被视为有效数据,复制到cmpbuff
。
- 从
4. 保存与清理
- 将处理后的数据写入文件
recv.txt
。 - 关闭文件描述符和套接字。
潜在问题与改进建议
-
缓冲区溢出风险
- 问题 :
buff
和cmpbuff
是固定大小,未检查strncat
是否越界。 - 建议:增加长度检查,或改用动态内存分配。
- 问题 :
-
分块编码处理不完整
- 问题 :代码假设数据仅包含一个分块(如
5\r\nhello\r\n0\r\n
),可能漏掉多分块数据。 - 建议 :循环解析所有分块,处理格式如
长度\r\n数据\r\n
。
- 问题 :代码假设数据仅包含一个分块(如
-
结束标志可靠性
- 问题 :若
0\r\n
被拆分成多次接收(如0\r
和\n
),可能无法正确退出。 - 建议 :在
buff
中全局搜索0\r\n
,而非仅检查tmpbuff
。
- 问题 :若
real_time_weather()
函数
cpp
void real_time_weather()
{
int ret = -1;
int fd = -1;
char *buffer = NULL;
ssize_t bytes_read = 0;
CreateTcpClient(); // 创建新的连接
if(!(buffer = malloc(BUFFER_SIZE))){
perror("malloc fail");
return;
}
if((fd = open("recv.txt", O_RDONLY)) == -1){
perror("open fail");
return;
}
if((bytes_read = read(fd, buffer, BUFFER_SIZE)) <= 0){
fprintf(stderr, "Read failed or empty file\n");
return;
}
buffer[bytes_read] = '\0'; // 确保字符终止
cJSON *root = cJSON_Parse(buffer);
if(!root){
fprintf(stderr, "JSON parse failed\n");
return;
}
// 使用结构体定义字段映射
struct FieldMapping {
const char *json_key;
const char *display_name;
int required; // 1表示必须字段
} fields[] = {
{"days", "日期", 1},
{"week", "星期", 1},
{"citynm", "城市", 1},
{"temperature_curr", "当前温度", 1},
{"temp_high", "最高温度", 0},
{"temp_low", "最低温度", 0},
{"weather", "天气状况", 1},
{"humidity", "当前湿度", 0},
{"humi_high", "最大湿度", 0},
{"humi_low", "最小湿度", 0},
{"wind", "风向", 1},
{"winp", "风力", 1},
{"aqi", "PM2.5", 0}
};
cJSON *result = cJSON_GetObjectItem(root, "result");
if(!result){
fprintf(stderr, "Missing result object\n");
cJSON_Delete(root);
return;
}
// 遍历所有字段定义
for(size_t i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
{
cJSON *item = cJSON_GetObjectItem(result, fields[i].json_key);
printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[i].display_name);
if(item && item->valuestring)
{
printf(COLOR_VALUE"%s\n"COLOR_RESET, item->valuestring);
}
else
{
printf(COLOR_WARN"N/A\n"COLOR_RESET);
if(fields[i].required)
{
fprintf(stderr, "Missing required field: %s\n", fields[i].json_key);
cJSON_Delete(root);
return;
}
}
}
ret = 0;
free(buffer);
cJSON_Delete(root);
close(fd);
}
亮点分析补充与优化建议
1. 模块化设计
- 优势 :
将天气查询功能封装为独立函数real_time_weather()
,逻辑边界清晰,符合"高内聚"原则。 - 改进空间 :
- 单一职责原则 :当前函数承担了网络连接、文件操作、JSON解析、数据展示等多个职责,可进一步拆分为子函数(如
fetch_weather_data()
、parse_json()
、display_weather()
)。 - 依赖解耦 :文件
recv.txt
作为数据传递媒介,属于硬编码依赖,建议改用内存直接传递(如通过函数参数传递数据缓冲区)。
- 单一职责原则 :当前函数承担了网络连接、文件操作、JSON解析、数据展示等多个职责,可进一步拆分为子函数(如
2. 动态内存管理
- 优势 :
使用malloc
动态分配内存,避免栈溢出风险,适应大数据场景。 - 改进空间 :
- 内存泄漏风险 :错误处理路径中未完全释放内存(如
malloc
成功但open
失败时未free(buffer)
),需用goto
或cleanup
标签统一释放资源。 - 缓冲区溢出 :
read(fd, buffer, BUFFER_SIZE)
未检查bytes_read
是否等于BUFFER_SIZE
,可能导致数据截断或越界。
- 内存泄漏风险 :错误处理路径中未完全释放内存(如
3. 错误处理
- 优势 :
覆盖文件操作、内存分配、JSON解析等关键错误点,避免程序崩溃。 - 改进空间 :
- 错误码统一管理 :
ret
变量未被实际使用,建议通过返回值或全局错误码明确错误类型。 - 错误信息分级:区分"致命错误"(如必填字段缺失)与"警告信息"(如可选字段缺失),避免冗余报错。
- 错误码统一管理 :
4. 数据解析
- 优势 :
使用cJSON
库解析复杂JSON数据,通过结构体FieldMapping
实现字段映射,扩展性强。 - 改进空间 :
- 字段类型校验 :未检查JSON字段实际类型(如数值型字段可能被误读为字符串),需增加
cJSON_IsString
/cJSON_IsNumber
等校验。 - 嵌套结构处理 :若JSON层级更深(如
result
下包含嵌套对象),需支持递归解析。
- 字段类型校验 :未检查JSON字段实际类型(如数值型字段可能被误读为字符串),需增加
5. 用户输出
- 优势 :
ANSI颜色代码提升可读性,字段映射表确保输出完整性。 - 改进空间 :
- 跨平台兼容性:Windows终端默认不支持ANSI颜色,需通过条件编译适配。
- 格式化输出 :使用固定宽度(
%-8s
)可能导致长字段显示不全,建议动态调整列宽。
难点分析与解决方案
1. JSON数据解析
- 核心挑战 :
- 字段路径依赖性强(如直接访问
root->result->temperature_curr
),若服务器返回结构变化,需修改代码。 - 未处理特殊字符(如转义字符
\"
)或Unicode编码(如\u4e2d\u6587
)。
- 字段路径依赖性强(如直接访问
- 解决方案 :
- 使用
cJSON_GetObjectItemCaseSensitive
避免大小写敏感问题。 - 通过
cJSON_Print
标准化输出,确保转义字符正确处理。
- 使用
2. 网络通信
- 核心挑战 :
CreateTcpClient()
实现未展示,可能隐藏连接超时、重试等逻辑缺失。- 未处理HTTP协议细节(如状态码检查、分块传输编码)。
- 解决方案 :
- 封装HTTP客户端库(如
libcurl
)简化网络操作。 - 增加超时机制和重试逻辑,提升鲁棒性。
- 封装HTTP客户端库(如
3. 文件操作
- 核心挑战 :
- 文件读写与网络通信耦合,可能导致数据不同步(如文件未及时刷新)。
- 解决方案 :
- 使用内存映射文件(
mmap
)或管道(pipe
)替代临时文件。 - 增加文件锁(
flock
)避免多进程竞争。
- 使用内存映射文件(
future_weather()
函数
cpp
void future_weather()
{
int ret = -1;
int fd = -1;
char *buffer = NULL;
ssize_t bytes_read = 0;
CreateTcpClient(); // 创建新的连接
if(!(buffer = malloc(BUFFER_SIZE))){
perror("malloc fail");
return;
}
if((fd = open("recv.txt",O_RDONLY)) == -1){
perror("open fail");
return;
}
if((bytes_read = read(fd,buffer,BUFFER_SIZE)) <= 0){
fprintf(stderr, "Read failed or empty file\n");
return;
}
buffer[bytes_read] = '\0';//确保字符终止
cJSON *root = cJSON_Parse(buffer);
if(!root){
fprintf(stderr, "JSON parse failed\n");
return;
}
// 使用结构体定义字段映射
struct FieldMapping {
const char *json_key;
const char *display_name;
int required; // 1表示必须字段
} fields[] = {
{"days", "日期", 1},
{"week", "星期", 1},
{"citynm", "城市", 1},
{"temp_high", "最高温度", 0},
{"temp_low", "最低温度", 0},
{"weather", "天气状况", 1},
{"wind", "风向", 1},
{"winp", "风力", 1},
};
cJSON *result = cJSON_GetObjectItem(root,"result");
if(!result){
fprintf(stderr, "Missing result object\n");
cJSON_Delete(root);
}
int i = 0;
cJSON *item;
cJSON_ArrayForEach(item,result)
{
printf(COLOR_HEAD"=============第%d天气预报=============\n"COLOR_RESET,i+1);
// 遍历所有字段定义
for (size_t j = 0; j < sizeof(fields)/sizeof(fields[0]); j++) {
cJSON *field = cJSON_GetObjectItem(item, fields[j].json_key);
printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[j].display_name);
if (field && field->valuestring) {
printf(COLOR_VALUE"%s\n"COLOR_RESET, field->valuestring);
} else {
printf(COLOR_WARN"N/A\n"COLOR_RESET);
if (fields[j].required) {
fprintf(stderr, "Missing required field: %s\n", fields[j].json_key);
}
}
}
i++;
}
cJSON_Delete(root);
free(buffer);
close(fd);
}
功能概述
该函数用于获取并展示未来多天的天气预报数据,整体流程如下:
- 建立TCP连接获取数据(依赖
CreateTcpClient()
)。 - 从文件
recv.txt
读取JSON格式的天气数据。 - 解析JSON数据并格式化输出未来多天的天气信息。
亮点与改进建议
1. 核心亮点
-
数据遍历逻辑
使用
cJSON_ArrayForEach
遍历天气预报的多个结果,支持动态数量的天气预报条目,灵活性较高。cJSON_ArrayForEach(item, result) { printf("第%d天天气预报", i+1); // 遍历字段并输出 }
-
字段映射复用
复用
FieldMapping
结构体定义字段映射关系,与real_time_weather()
保持一致性,降低维护成本。 -
用户交互友好
通过颜色区分标题、字段名和数值,输出层次清晰:
printf(COLOR_HEAD"=============第%d天气预报=============\n"COLOR_RESET, i+1);
2. 潜在问题与改进
问题1:内存与资源泄漏
-
风险点
- 文件打开失败(
open
返回-1
)时,未释放malloc
分配的buffer
。 - JSON解析失败时,未关闭文件描述符
fd
。
- 文件打开失败(
-
修复建议
使用统一清理逻辑确保资源释放:
void future_weather() { int fd = -1; char *buffer = NULL; cJSON *root = NULL; // 初始化代码... // 错误处理跳转标签 cleanup: if (buffer) free(buffer); if (root) cJSON_Delete(root); if (fd != -1) close(fd); }
问题2:JSON类型安全性
-
风险点
直接访问
field->valuestring
,若字段值为数值类型(如温度),会导致空指针或错误输出。// 错误示例:若temp_high为数值,valuestring为NULL printf("%s", field->valuestring);
-
修复建议
增加类型检查,支持数值和字符串:
if (cJSON_IsString(field)) { printf("%s", field->valuestring); } else if (cJSON_IsNumber(field)) { printf("%d", field->valueint); }
问题3:数据完整性风险
-
风险点
假设
result
为数组类型,若服务器返回非数组数据(如空对象),程序会错误遍历。cJSON *result = cJSON_GetObjectItem(root, "result"); cJSON_ArrayForEach(item, result); // 若result非数组,崩溃!
-
修复建议
验证数据类型:
if (!cJSON_IsArray(result)) { fprintf(stderr, "Invalid result format: expected array\n"); goto cleanup; }
问题4:代码冗余
-
风险点
future_weather
与real_time_weather
存在大量重复代码(如文件操作、JSON解析),违反DRY原则。 -
修复建议
抽象公共逻辑为独立函数:
// 公共函数:读取文件到缓冲区 char* read_weather_data(const char *filename) { int fd = open(filename, O_RDONLY); // 读取并返回buffer... } // 公共函数:解析并打印天气字段 void print_weather_fields(cJSON *item, struct FieldMapping *fields, size_t count) { // 遍历字段并输出... }
难点与解决方案
1. 动态数组遍历
- 难点
需处理未知数量的天气预报条目,且每个条目需完整解析字段。 - 方案
使用cJSON_ArrayForEach
宏安全遍历数组,避免手动索引越界。
2. 多层级JSON解析
-
难点
若JSON结构复杂(如嵌套对象),需递归解析。 -
方案
设计递归解析函数,处理嵌套结构:void parse_nested(cJSON *node, int depth) { if (cJSON_IsObject(node)) { cJSON_ArrayForEach(child, node) { parse_nested(child, depth + 1); } } }