使用HTTP协议在k780网站上获取天气-----天气预报


项目介绍


本期主要使用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网站的接口查询历史天气是需要收费的,所以在本期分享中我用的是测试接口,没有付费;
最后,各位小伙伴们如果喜欢我的分享可以点赞收藏哦,你们的认可是我创作的动力,一起加油!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值