linux---天气爬虫
代码概述
这段代码实现了一个天气查询系统,支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型,并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求,并解析返回的 JSON 数据来展示天气信息。
#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;
}#endifvoid 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}; // 确保初始化为全0char 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() 函数
void RecvSendWeather(void)
{int fd = 0;char buff[BUFFER_SIZE] = {0}; // 确保初始化为全0char 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() 函数
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() 函数
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);}} }
相关文章:
linux---天气爬虫
代码概述 这段代码实现了一个天气查询系统,支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型,并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求,并解析返回的 JSON 数据来展示天气信息。 #in…...
字节顺序(大小端序)
在弄明白字节顺序之前先了解一下一些基础概念. 基础概念 字节(byte): 字节是计算机中数据处理的基本单位,通常由8个位组成,即1字节等于8位。一个字节可以存储一个ASCII码,两个字节可以存放一个汉字国标…...
可复用的 Vue 轮播图组件
大家好,今天我想和大家分享一下如何开发一个通用的 Vue 轮播图组件。轮播图在各种网站中都很常见,无论是展示产品、活动还是文章,都能派上用场。我们今天要实现的这个组件会具备良好的可配置性和易用性,同时保证代码的可维护性。 …...
AI编程: 一个案例对比CPU和GPU在深度学习方面的性能差异
背景 字节跳动正式发布中国首个AI原生集成开发环境工具(AI IDE)——AI编程工具Trae国内版。 该工具模型搭载doubao-1.5-pro,支持切换满血版DeepSeek R1&V3, 可以帮助各阶段开发者与AI流畅协作,更快、更高质量地完…...
Linux红帽:RHCSA认证知识讲解(五)从红帽和 DNF 软件仓库下载、安装、更新和管理软件包
Linux红帽:RHCSA认证知识讲解(五)从红帽和 DNF 软件仓库下载、安装、更新和管理软件包 前言一、DNF 软件包管理基础1.1 核心操作命令安装软件包卸载软件包重新安装软件包 1.2 软件仓库原理 二、配置自定义软件仓库步骤 1:清理默认…...
云上特权凭证攻防启示录:从根账号AK泄露到安全体系升级的深度实践
事件全景:一场持续17分钟的云上攻防战 2025年3月9日15:39,阿里云ActionTrail日志突现异常波纹——根账号acs:ram::123456789:root(已脱敏)从立陶宛IP(164.92.91.227)发起高危操作。攻击者利用泄露的AccessKey(AK)在17分钟内完成侦察→提权→持久化攻击链,完整操作序列…...
从3b1b到课堂:教育3D化的理想与现实鸿沟
从3b1b到课堂:教育3D化的理想与现实鸿沟 3Blue1Brown(3b1b)凭借精妙的三维动画与直观的知识可视化,重新定义了数学教育的可能性。然而,当前教育实践中,3D技术的渗透仍显不足,多数课堂停留在平面…...
FPGA入门教程
引言 FPGA(Field-Programmable Gate Array,现场可编程门阵列)是一种灵活且强大的硬件设备,广泛应用于数字电路设计、信号处理、嵌入式系统等领域。与传统的ASIC(专用集成电路)不同,FPGA允许用户…...
Liunx系统 : 进程间通信【IPC-Shm共享内存】
文章目录 System V共享内存创建共享内存shmget 控制共享内存shmctl shm特性 System V System V是Liunx中的重要的进程间通信机制,它包括(shm)共享内存,(msg)消息队列和(sem)信号量。…...
KafkaRocketMQ
Kafka 消息生产与消费流程 1. 消息生产 生产者创建消息: 指定目标 Topic、Key(可选)、Value。可附加 Header 信息(如时间戳、自定义元数据)。 选择分区(Partition): 若指定 Key&am…...
HarmonyOS Next 中的状态管理
在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkU…...
基于qiime2的16S数据分析全流程:从导入数据到下游分析一条龙
目录 创建metadata 把数据导入qiime2 去除引物序列 双端合并 (dada2不需要) 质控 (dada2不需要) 使用deblur获得特征序列 使用dada2生成代表序列与特征表 物种鉴定 可视化物种鉴定结果 构建进化树(ITS一般不构建进化树…...
【软件测试开发】:软件测试常用函数1.0(C++)
1. 元素的定位 web⾃动化测试的操作核⼼是能够找到⻚⾯对应的元素,然后才能对元素进⾏具体的操作。 常⻅的元素定位⽅式⾮常多,如id,classname,tagname,xpath,cssSelector 常⽤的主要由cssSelector和xpath…...
vue2项目修改浏览器显示的网页图标
1.准备一个新的图标文件,通常是. ico格式,也可以是. Png、. Svg等格式 2.将新的图标文件(例如:faviconAt.png)放入项目的public文件夹中。如下图 public文件夹中的所有文件都会在构建时原样复制到最终的输出目录(通常是dist) 3. 修改vue项目…...
开源、创新与人才发展:机器人产业的战略布局与稚晖君成功案例解析
目录 引言 一、开源:机器人产业的战略布局 促进技术进步和生态建设 吸引人才和合作伙伴 建立标准和网络效应 降低研发风险与成本 二、稚晖君:华为"天才少年计划"的成功典范 深厚的技术积累与动手能力 强烈的探索和创新意识 持续公开…...
线程相关作业
1.创建两个线程,分支线程1拷贝文件的前一部分,分支线程2拷贝文件的后一部分 #include "head.h"#define BUFFER_SIZE 1024// 线程参数结构体,包含文件名和文件偏移量 typedef struct {FILE *src_file;FILE *dest_file;long start_o…...
通义万相2.1开源版本地化部署攻略,生成视频再填利器
2025 年 2 月 25 日晚上 11:00 通义万相 2.1 开源发布,前两周太忙没空搞它,这个周末,也来本地化部署一个,体验生成效果如何,总的来说,它在国内文生视频、图生视频的行列处于领先位置,…...
【模拟CMOS集成电路设计】带隙基准(Bandgap)设计与仿真(基于运放的电流模BGR)
【模拟CMOS集成电路设计】带隙基准(Bandgap)设计与仿真 前言工程文件&部分参数计算过程,私聊~ 一、 设计指标指标分析: 二、 电路分析三、 仿真3.1仿真电路图3.2仿真结果(1)运放增益(2)基准温度系数仿真(3)瞬态启动仿真(4)静态…...
如何选择国产串口屏?
目录 1、迪文 2、淘晶驰 3、广州大彩 4、金玺智控 5、欣瑞达 6、富莱新 7、冠显 8、有彩 串口屏,顾名思义,就是通过串口通信接口(如RS232、RS485、TTL UART等)与主控设备进行通信的显示屏。其核心功能是显示信息和接收输入…...
Solana中的程序派生地址(PDAs):是什么,为什么,以及如何?
程序派生地址 (PDA) 在 Solana 中的应用:什么、为什么和如何? 在学习 Solana 时,你会经常听到关于 程序派生地址 (PDAs) 的讨论。它们就像这样 —— 强大、多功能,而且最重要的是,稍微被误解。如果你是一个开发者&…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
