Linux还真是逐步熟悉中,现在才了解到Linux即没有原生的GUI,也没有应用层协议栈,所以要实现HTTP应用,必须利用TCP然后自己封装HTTP数据包。本篇即记录封装HTTP数据包,到心知天气请求天气信息的案例实现过程。
心知天气应该是当下国内使用很普遍的一个天气数据站点。相关注册和使用过程,这里就不再啰嗦了,不清楚的朋友可以自己到官网上查看(https://www.seniverse.com/)。
本例仅测试实时天气数据获取,天气相关数据只有“状态(晴朗之类)”和“气温”,请求接口地址如下:
https://api.seniverse.com/v3/weather/now.json?key=your_api_key&location=beijing&language=zh-Hans&unit=c
可以看到请求地址给的是域名,TCP连接需要直接给IP地址,所以用ping来获取其IP为“116.62.81.138”,端口自然是80。
得到IP地址后,先不着急编程,通过网络助手实验一把,具体过程是:选择TCP Client,连接对方IP和端口(116.62.81.138:80),然后将请求地址前加上方法字串“GET”,结尾还要有两个回车换行“\r\n\r\n”。初次测试时,忘记了回车换行符没有成功,加上后就好了。
封装好的数据包是:“GET https://api.thinkpage.cn/v3/weather/now.json?key=yourkey&location=tianjin&language=en&unit=c\r\n\r\n”。
请求到的数据是JSON格式,贴到Json.cn(https://www.json.cn/)的在线工具里,可以更清晰的看到其结构。
可以看到请求实时数据(now.json),得到一个JSON对象,包含一个“results”引导的JSON数组,且数组只有一个元素,元素中又包含“location”、“now”和“last_update”三个JSON对象,内部还有键值对。
既然是开发Linux API的C程序,当然利用cJSON库来帮助进行数据解析了。本人使用的库是从网上搜到的一个百度网盘分享。
链接:https://pan.baidu.com/s/1DQynsdlNyIvsVXmf4W5b8Q
提取码:ww4z
具体思路就是建立TCP Client连接心知天气的Server,然后发送请求包,得到响应包,解析并打印出结果,案例比较简单做成单次的——开启即运行到底,代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "cJSON.h"
#define SERVER_IP "116.62.81.138"
#define SERVER_PORT 80
#define NOW "now.json"
#define DAILY "daily.json"
#define API_KEY "yourapikey"
#define CITY "tianjin"
#define REQ_PACK "GET https://api.thinkpage.cn/v3/weather/%s?key=%s&location=%s&language=en&unit=c\r\n\r\n"
#define N 1024
#define errlog(errmsg) do{ perror(errmsg);\
printf("----%s----%s----%d----\n", __FILE__, __func__, __LINE__);\
return -1;\
} while(0)
//struct for weather data
typedef struct {
char id[16];
char name[32];
char country[16];
char path[64];
char timezone[32];
char tz_offset[16];
char text[16];
char code[4];
char temp[8];
char last_update[32];
} weather_t;
//parse function & print weather_t data function
void aita_ParseJsonNow(char *json, weather_t *w);
void aita_PrintWeather(weather_t *w);
int main(int argc, const char *argv[]) {
int sockfd;
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
char sendbuf[N] = "";
char recvbuf[N] = "";
weather_t weather = {0};
//create socket
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
errlog("socket error");
}
//connect to server of seniverse.com
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serveraddr.sin_port = htons(SERVER_PORT);
if((connect(sockfd, (struct sockaddr*)&serveraddr, addrlen)) < 0) {
errlog("connect error");
}
//build & send request package
sprintf(sendbuf, REQ_PACK, NOW, API_KEY, CITY);
if(send(sockfd, sendbuf, N, 0) < 0) {
errlog("send error");
}
//waiting server response
if(recv(sockfd, recvbuf, N, 0) < 0) {
errlog("recv error");
}
printf("recv: %s\n", recvbuf);
//parse & print data
aita_ParseJsonNow(recvbuf, &weather);
aita_PrintWeather(&weather);
close(sockfd);
return 0;
}
void aita_ParseJsonNow(char *msg, weather_t *w) {
cJSON *json, *ja, *jo, *josub, *item;
json = cJSON_Parse(msg); //parse string to cJSON type
if(json == NULL) {
printf("json type cast error: %s", cJSON_GetErrorPtr());
return;
} else {
printf("parse now pack\n");
if((ja=cJSON_GetObjectItem(json, "results")) != NULL) { //get results array
if((jo=cJSON_GetArrayItem(ja, 0)) != NULL) { //get array[0](the only item)
//get location object
if((josub=cJSON_GetObjectItem(jo, "location")) != NULL) {
if((item=cJSON_GetObjectItem(josub, "id")) != NULL) {
memcpy(w->id, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "name")) != NULL) {
memcpy(w->name, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "country")) != NULL) {
memcpy(w->country, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "path")) != NULL) {
memcpy(w->path, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "timezone")) != NULL) {
memcpy(w->timezone, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "timezone_offset")) != NULL) {
memcpy(w->tz_offset, item->valuestring, strlen(item->valuestring));
}
}
//get now object
if((josub=cJSON_GetObjectItem(jo, "now")) != NULL) {
if((item=cJSON_GetObjectItem(josub, "text")) != NULL) {
memcpy(w->text, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "code")) != NULL) {
memcpy(w->code, item->valuestring, strlen(item->valuestring));
}
if((item=cJSON_GetObjectItem(josub, "temperature")) != NULL) {
memcpy(w->temp, item->valuestring, strlen(item->valuestring));
}
}
//get last_update object
if((josub=cJSON_GetObjectItem(jo, "last_update")) != NULL) {
memcpy(w->last_update, josub->valuestring, strlen(josub->valuestring));
}
}
}
}
//delete original json pack free memory
cJSON_Delete(json);
return;
}
void aita_PrintWeather(weather_t *w) {
printf("id: %s\n", w->id);
printf("name: %s\n", w->name);
printf("country: %s\n", w->country);
printf("path: %s\n", w->path);
printf("timezone: %s\n", w->timezone);
printf("timezone_offset: %s\n", w->tz_offset);
printf("text: %s\n", w->text);
printf("code: %s\n", w->code);
printf("temperature: %s\n", w->temp);
printf("last_update: %s\n", w->last_update);
}
项目路径中建立了源文件main.c,编写上述代码,并导入cJSON.c和cJSON.h,编译命令为:“riscv64-unknown-linux-gnu-gcc main.c cJSON.c -o weather -lm”。因为cJSON会用到math库,而它需要“-lm”来动态链接。
本帖最后由 sonicfirr 于 2022-4-19 20:21 编辑