文章目录
- 项目介绍
- 一、项目任务
- 二、项目流程规划以及代码实现
- 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); //拼接url
sockfd = 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网站的接口查询历史天气是需要收费的,所以在本期分享中我用的是测试接口,没有付费;
最后,各位小伙伴们如果喜欢我的分享可以点赞收藏哦,你们的认可是我创作的动力,一起加油!