文章目录
- 项目介绍
- 一、项目任务
- 二、项目流程规划以及代码实现
- 1.总流程
- 2.引入库
- 3.总体流程相关函数
- 三、功能函数的实现
- 1. TCP函数
- 2. 输入城市信息
- 3. 查询今天天气
- 4. 查询未来一周天气
- 5. 查询历史天气
- 6. 退出
- 总结
项目介绍
本期主要使用TCP网络编程实现天气预报的功能,这个项目旨在于增进对于TCP编程的掌握以及应用,在这个项目中页用到了一种常见的数据格式—cjson数据格式,能够在这个项目中学会使用cjson数据的解析和使用对日后的工作应该是非常有益的;那么不止这些,还有c语言哦,遇到的棘手的问题几乎都是出自于对于接收到数据的处理,那么从这个项目中处理这个问题(在一串长数据中解析出自己需要的数据)的能力也是需要有的;如果对自己字符串处理的能力不是很自信,那么大家可以尝试来做一下这个项目;
一、项目任务
项目需要实现以下几个功能:
(1)能够输出用户操作的菜单如下:
1.配置城市 2.查看实时天气信息 3.查看未来一周天气 4.查看历史天气信息 5.退出
(2)输入序号对应实现相应的功能
二、项目流程规划以及代码实现
1.总流程
本项目的总体流程图如下:
每个项目一定是先从整体来把握,再化身庖丁,进行进一步的解析;
2.引入库
首先呢,先把所有的头文件给大家贴出来,其中包含了函数声明:
#ifndef __HEAD_H__
#define __HEAD_H__#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "cJSON.h"struct sockaddr_in sendaddr;
char city[128];extern int openLogFile(void);
extern int writeToLog(const char *err);
extern int closeLog(void);extern int showTerminal(void);
extern int choiceMode(int n);extern int inputCity(char *city);extern int createTcpConnect(const char *pIp, int Port);
extern int sendRequestCmd(int sockfd, char *purl);
extern int recvData(int sockfd, char *pRecvbuf, int len);extern int saveCjsonData(const char *data);
extern char*changeTodata(char *data);
extern int parseCjsonDataOfToday(char *data);
extern int parseCjsonDataOfFuture(char *data);
extern int parseCjsonDataOfHistory(char *data);
extern int searchTodayWeather(const char *city);
extern int searchFutureWeather(const char *city);
extern int searchHistoryWeather(const char *city);
#endif
3.总体流程相关函数
先来看下主函数吧:
#include "head.h"int main(int argc, const char *argv[])
{ int n = 0;openLogFile();while (1){showTerminal();printf("请选择:");scanf("%d", &n);getchar();choiceMode(n);}closeLog();return 0;
}
以上代码的步骤是打开日志文件—循环打印终端—用户进行功能选择—循环结束后关闭日志文件;
下面给出日志文件的所有相关代码:
#include "head.h"FILE *fp = NULL;
//打开日志文件
int openLogFile(void)
{fp = fopen("./log.txt", "a");if (NULL == fp){perror("fail to fopen");return -1;}return 0;
}//写入日志信息
int writeToLog(const char *err)
{char tmpbuff[1024] = {0};time_t t;struct tm *pp;ssize_t nsize = 0;time(&t);pp = localtime(&t);memset(tmpbuff, 0, sizeof(tmpbuff));sprintf(tmpbuff, "[%4d-%02d-%02d %02d:%02d:%02d]:%s\n", pp->tm_year+1900, pp->tm_mon+1, pp->tm_mday,pp->tm_hour, pp->tm_min, pp->tm_sec, err);printf("%s\n", tmpbuff);nsize = fwrite(tmpbuff, strlen(tmpbuff), 1, fp);if (-1 == nsize){perror("fail to fwrite");return -1;}fflush(fp);return 0;
}//关闭日志文件
int closeLog(void)
{fclose(fp);return 0;
}
打印终端的函数如下:
//打印终端
int showTerminal(void)
{printf("============================\n");printf(" 天气预报 APP \n");printf("============================\n");printf("1.输入需要查询天气的城市\n");printf("2.查看当前天气\n");printf("3.查看未来一周天气\n");printf("4.查看历史天气\n");printf("5.退出\n");printf("============================\n");return 0;
}
进行模式选择如下:
//进行菜单模式选择
int choiceMode(int n)
{switch (n){case 1 : inputCity(city);break;case 2 : searchTodayWeather(city);break;case 3 : searchFutureWeather(city);break;case 4 : searchHistoryWeather(city);break;case 5 : exit(0);break;}return 0;
}
三、功能函数的实现
1. TCP函数
下面所列出的是所有的TCP通信的相关函数:
//进行TCP链接请求
int createTcpConnect(const char *pIp, int Port)
{int sockfd = 0;int ret = 0;sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd){writeToLog("fail to socket");return -1;}sendaddr.sin_family = AF_INET;sendaddr.sin_port = htons(Port);sendaddr.sin_addr.s_addr = inet_addr(pIp);ret = connect(sockfd, (struct sockaddr *)&sendaddr, sizeof(sendaddr));if (-1 == ret){writeToLog("fail to connect!");}return sockfd;
}//向网页发送命令(以GET方法获取)
int sendRequestCmd(int sockfd, char *purl)
{char tmpbuff[4096] = {0};ssize_t nsize = 0;writeToLog("进来了");sprintf(tmpbuff, "GET %s HTTP/1.1\r\n", purl);sprintf(tmpbuff, "%sHost: api.k780.com\r\n", tmpbuff);sprintf(tmpbuff, "%sUser-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n", tmpbuff);sprintf(tmpbuff, "%sAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n", tmpbuff);sprintf(tmpbuff, "%sAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\n", tmpbuff);sprintf(tmpbuff, "%sAccept-Encoding: gzip, deflate\r\n", tmpbuff);sprintf(tmpbuff, "%sConnection: keep-alive\r\n\r\n", tmpbuff);nsize = send(sockfd, tmpbuff, strlen(tmpbuff), 0);if (-1 == nsize){writeToLog("fail to send cmd!");return -1;}writeToLog("send success!");return sockfd;
}/*接收数据(此处接收数据有一个弊端在于不能循环用recv去接收k780的数据,因为recv默认是阻塞模式,
它接完后如果对方没有断开,那么它就会持续阻塞导致程序陷入等待状态)
所以最终选择每次只接受一次数据
*/
int recvData(int sockfd, char *pRecvbuf, int len)
{ssize_t nsize = 0;FILE *fp = NULL;fp = fopen("./cjson.txt", "w");if (NULL == fp){writeToLog("write to cjson.txt failed!");return -1;}writeToLog("进来收取了!"); nsize = recv(sockfd, pRecvbuf, len - 1, 0);if (nsize <= 0) {writeToLog("fail to recv!");return -1;}fwrite(pRecvbuf, 1, nsize, fp);fflush(fp);writeToLog("recv success!");fclose(fp);return 0;
}
2. 输入城市信息
这里需要注意city在每个功能中都会使用,所以采用全局变量定义;
//配置城市信息
int inputCity(char *city)
{char *ptmp = NULL;printf("请输入:");memset(city, 0, sizeof(city));ptmp = fgets(city, 128, stdin);if (NULL == ptmp){writeToLog("fgets failed!");return -1;}city[strlen(city) - 1] = 0;return 0;
}
3. 查询今天天气
查询天气的流程为:创建套接字请求TCP链接—发送请求命令—接收k780平台反馈的天气信息(含有http包头的cjson数据);
//实现查询今日天气功能
int searchTodayWeather(const char *city)
{int sockfd = 0;char tmpbuff[4096] = {0};//存放接收到的数据信息char *pcontent = NULL;sockfd = createTcpConnect("103.205.5.249", 80); //进行TCP链接请求(103.205.5.249k780提供给用户专门用于访问的ip)if (-1 == sockfd){writeToLog("fail to createTcpConnect!");}writeToLog("sockfd success!");printf("%s\n", city);sprintf(tmpbuff, "/?app=weather.today&weaid=%s&appkey=67618&sign=d83f5d08cb5dea32925dec2cbb077211&format=json", city); //拼接urlsockfd = sendRequestCmd(sockfd, tmpbuff); //向k780网站发送请求memset(tmpbuff, 0, sizeof(tmpbuff));recvData(sockfd, tmpbuff, sizeof(tmpbuff)); //接受数据信息printf("ok!\n");pcontent = changeTodata(tmpbuff); //从收到的所有数据中解析得到完整的cjson数据parseCjsonDataOfToday(pcontent); //对cjson数据进行解析获取需要的信息close(sockfd);return 0;
}
此函数实现的主要功能是从获取到网页含有http包头的数据中解析出cjson数据;
如下图所示,是一个天气的所有信息:
解析函数如下:
//解析今天的cjson数据
int parseCjsonDataOfToday(char *data)
{cJSON *pjson = NULL;cJSON *presult = NULL;cJSON *pvalue = NULL;pjson = cJSON_Parse(data);if (NULL == pjson){writeToLog("cJSON_Parse failed!");return -1;}presult = cJSON_GetObjectItem(pjson, "result");if (NULL == presult){writeToLog("cJSON_GetObjectItem failed!");return -1;}printf("=========================\n");printf(" 天气预报 \n");printf("=========================\n");pvalue = cJSON_GetObjectItem(presult, "days");printf("日期:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(presult, "week");printf("星期:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(presult, "citynm");printf("城市:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(presult, "temperature_curr");printf("温度:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(presult, "humidity");printf("湿度:%s\n", pvalue->valuestring);return 0;
}
//从获取到网页含有http包头的数据中解析出cjson数据
char*changeTodata(char *data)
{char *begin = NULL;char *end = NULL;char tmpbuff[4096] = {0};begin = strstr(data, "\r\n\r\n");begin += 4;begin = strstr(begin, "\r\n");begin += 2;end = strstr(begin, "\r\n");strncpy(tmpbuff, begin, end - begin + 1);strcpy(data, tmpbuff);return data;
}
结果如下,得到需要的数据:
打印今天天气的功能如下:
4. 查询未来一周天气
此功能和上一个功能的实现是非常相似的,不同点在于数据格式不同,所以解析的方法也不同;
//实现查找未来一周天气功能
int searchFutureWeather(const char *city)
{int sockfd = 0;char tmpbuff[10 * 4096] = {0};FILE *fp = NULL;off_t len;char *pcontent = NULL;sockfd = createTcpConnect("103.205.5.249", 80);if (-1 == sockfd){writeToLog("fail to createTcpConnect!");}writeToLog("sockfd success!");printf("%s\n", city);memset(tmpbuff, 0, sizeof(tmpbuff));sprintf(tmpbuff, "/?app=weather.future&weaid=%s&appkey=67618&sign=d83f5d08cb5dea32925dec2cbb077211&format=json", city);sendRequestCmd(sockfd, tmpbuff);memset(tmpbuff, 0, sizeof(tmpbuff));recvData(sockfd, tmpbuff, sizeof(tmpbuff));pcontent = changeTodata(tmpbuff);parseCjsonDataOfFuture(pcontent);close(sockfd);return 0;
}
解析函数如下:
//解析未来一周的cjson数据
int parseCjsonDataOfFuture(char *data)
{cJSON *pjson = NULL;cJSON *presult = NULL;cJSON *pvalue = NULL;cJSON *parray = NULL;int i = 0;pjson = cJSON_Parse(data);if (NULL == pjson){writeToLog("cJSON_Parse failed!");return -1;}presult = cJSON_GetObjectItem(pjson, "result");if (NULL == presult){writeToLog("cJSON_GetObjectItem failed!");return -1;}printf("=========================\n");printf(" 天气预报 \n");printf("=========================\n");for (i = 0;i < 7; ++i){printf("=========================\n");parray = cJSON_GetArrayItem(presult, i);pvalue = cJSON_GetObjectItem(parray, "days");printf("日期:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(parray, "week");printf("星期:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(parray, "citynm");printf("城市:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(parray, "temperature");printf("温度:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(parray, "humidity");printf("湿度:%s\n", pvalue->valuestring);printf("=========================\n");}return 0;
}
实现功能的结果如下:
5. 查询历史天气
查询历史天气的cjson数据如下:
//实现查找历史天气功能
int searchHistoryWeather(const char *city)
{int sockfd = 0;char tmpbuff[30 * 4096] = {0};char tmpbuff1[30 * 4096] = {0};FILE *fp = NULL;off_t len;char *pcontent = NULL;sockfd = createTcpConnect("103.205.5.249", 80);if (-1 == sockfd){writeToLog("fail to createTcpConnect!");}writeToLog("sockfd success!");printf("%s\n", city);memset(tmpbuff, 0, sizeof(tmpbuff));sprintf(tmpbuff, "/?app=weather.history&weaid=1&dateYmd=20220101&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json");sendRequestCmd(sockfd, tmpbuff);memset(tmpbuff, 0, sizeof(tmpbuff));recvData(sockfd, tmpbuff, sizeof(tmpbuff));printf("tmpbuff = %s", tmpbuff);pcontent = changeTodata(tmpbuff);printf("pcontent = %s\n", pcontent);parseCjsonDataOfHistory(pcontent);close(sockfd);return 0;
}
由于历史cjson数据格式与前两种功能的cjson数据格式不同,所以具体解析方式也不相同;
//解析历史cjson数据
int parseCjsonDataOfHistory(char *data)
{cJSON *pjson = NULL;cJSON *presult = NULL;cJSON *pvalue = NULL;cJSON *parray = NULL;int i = 0;printf("data = %s\n", data);printf("=================================================================\n");pjson = cJSON_Parse(data);if (NULL == pjson){writeToLog("cJSON_Parse failed!");return -1;}presult = cJSON_GetObjectItem(pjson, "result");if (NULL == presult){writeToLog("cJSON_GetObjectItem failed!");return -1;}presult = cJSON_GetObjectItem(presult, "dtList");printf("=========================\n");printf(" 天气预报 \n");printf("=========================\n");for (i = 0;i < 12; ++i){printf("=========================\n");parray = cJSON_GetArrayItem(presult, i);pvalue = cJSON_GetObjectItem(parray, "upTime");printf("日期:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(parray, "wtTemp");printf("温度:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(parray, "wtHumi");printf("湿度:%s\n", pvalue->valuestring);pvalue = cJSON_GetObjectItem(parray, "wtNm");printf("天气:%s\n", pvalue->valuestring);printf("=========================\n");}return 0;
}
由于上面接取数据的时候只接收了12组数据,所以解析的部分结果如下:
6. 退出
我们需要做的操作非常简单,只需要exit(0);结束进程即可;
总结
本项目的所有功能实现在上文中均有展示,如果想提高网络编程以及字符串处理,http协议的同学可以上手练习一下这个项目,对于正在学习的小伙伴们来说是的非常有帮助的;本项目也有以下几个不足:
(1)在接收数据时只做到的单词接收,大家可以将recv改进为非阻塞模式解决这个问题;
(2)k780网站的接口查询历史天气是需要收费的,所以在本期分享中我用的是测试接口,没有付费;
最后,各位小伙伴们如果喜欢我的分享可以点赞收藏哦,你们的认可是我创作的动力,一起加油!