「socket應用」基於C語言的TCP天氣客戶端的實現

一、前言

上一篇筆記分享了 ,這一篇分享一個用C語言寫的、基於TCP的一個HTTP天氣客戶端的實現,這個一個控制檯應用程序,最終的界面如下:

「socket應用」基於C語言的TCP天氣客戶端的實現

二、天氣客戶端實現的要點

首先,需要說明的是,這份代碼是在windows系統下使用gcc6.3.0進行編譯的。

1、秘鑰

心知天氣:www.seniverse.com

我們完成這個實驗必須得到這個天氣網站(或者其它天氣網站)上面去註冊一個賬號才能使用它的天氣數據,註冊之後每個賬戶都會有一個私鑰,例如:

私鑰 SMEieQjde1C9eXnbE

這個是我們程序中需要用到。

2、IP和端口

上一節分享了socket的筆記,我們與服務端通信,需要知道三個重要的信息,分別是:

  1. IP地址
  2. 端口
  3. 傳輸方式

我這裡的心知天氣的IP是116.62.81.138,端口是80,傳輸方式是TCP,對應的代碼如下:

「socket應用」基於C語言的TCP天氣客戶端的實現

這裡的WEATHER_IP_ADDR對應的就是116.62.81.138,WEATHER_PORT對應的就是80。

3、GET請求

HTTP有幾種請求方法,我們這裡使用的是GET請求。查看心知天氣API文檔可知,請求地址示例為:

https://api.seniverse.com/v3/weather/now.json?key=your_api_key&location=beijing&language=zh-Hans&unit=c

這是一個天氣實況的請求地址示例,其有幾個重要的參數:

「socket應用」基於C語言的TCP天氣客戶端的實現

這裡的key是個很重要的參數,就是我們前面說的私鑰。

我們的天氣客戶端就是要往天氣服務端發送類似這樣的GET請求來獲取天氣數據,具體的請求方法示例為:

<code>GET https://api.seniverse.com/v3/weather/now.json?key=2owqvhhd2dd9o9f8&location=beijing&language=zh-Hans&unit=c/<code>

對應代碼如下:

「socket應用」基於C語言的TCP天氣客戶端的實現

這裡簡單複習一下sprintf函數的用法:

(1)函數功能:字符串格式化

(2)函數原型:int sprintf(char *string, char *format [,argument,...]);

string: 這是指向一個字符數組的指針,該數組存儲了 C 字符串。

format : 這是字符串,包含了要被寫入到字符串 str 的文本。

[argument]...:根據不同的 format 字符串,函數可能需要一系列的附加參數,每個參數包含了一個要被插入的值,替換了 format 參數中指定的每個 % 標籤。

(3)使用示例:

<code>sprintf(buf, "%s,%d", str, num);/<code>

假如此時str為"hello",num為5201314,則此時buf中的內容為:hello,5201314,需要注意的是buf的容量要足夠大。

4、天氣服務端返回的數據

天氣服務端給我們天氣客戶端返回的數據為JSON格式數據,可查閱往期筆記JSON的簡單認識。我們這個天氣客戶端只是實現了查詢此刻天氣(對應的數據包為now.json)及近三天天氣情況(對應的數據包為daily.json),如要查詢其他信息,可模仿我們這裡處理now.json和daily.json的方法,我們用cJson庫進行解析。


只要把cJSON.c與cJSON.h放到工程主程序所在目錄,然後在主程序中包含頭文件JSON.h即可引入該庫。如:

「socket應用」基於C語言的TCP天氣客戶端的實現

為了解析now.json和daily.json中的有用數據,我們建立如下結構體:

「socket應用」基於C語言的TCP天氣客戶端的實現

現在看一下now.json和daily.json的內容是怎樣的:

(1)now.json示例及解析:

now.json:

「socket應用」基於C語言的TCP天氣客戶端的實現


這裡實測了一下,我們普通用戶(因為沒充錢,哈哈~)申請的now.json數據中,now對象中只有如下三個鍵值對:

「socket應用」基於C語言的TCP天氣客戶端的實現


now.json的解析函數:

<code>/*******************************************************************************************************
** 函數: cJSON_NowWeatherParse,解析天氣實況數據
**------------------------------------------------------------------------------------------------------
** 參數: JSON:天氣數據包 result:數據解析的結果
** 返回: void
** 公號:嵌入式大雜燴
********************************************************************************************************/
static int cJSON_NowWeatherParse(char *JSON, Weather *result)

{
\tcJSON *json,*arrayItem,*object,*subobject,*item;
\t
\tjson = cJSON_Parse(JSON); //解析JSON數據包
\tif(json == NULL)\t\t //檢測JSON數據包是否存在語法上的錯誤,返回NULL表示數據包無效
\t{
\t\tprintf("Error before: [%s]\\n",cJSON_GetErrorPtr()); //打印數據包語法錯誤的位置
\t\treturn 1;
\t}
\telse
\t{
\t\tif((arrayItem = cJSON_GetObjectItem(json,"results")) != NULL); //匹配字符串"results",獲取數組內容
\t\t{
\t\t\tint size = cJSON_GetArraySize(arrayItem); //獲取數組中對象個數
#if DEBUG
\t\t\tprintf("cJSON_GetArraySize: size=%d\\n",size);
#endif
\t\t\tif((object = cJSON_GetArrayItem(arrayItem,0)) != NULL)//獲取父對象內容
\t\t\t{
\t\t\t\t/* 匹配子對象1:城市地區相關 */
\t\t\t\tif((subobject = cJSON_GetObjectItem(object,"location")) != NULL)
\t\t\t\t{
\t\t\t\t\t// 匹配id
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"id")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->id, item->valuestring,strlen(item->valuestring)); \t\t// 保存數據供外部調用
\t\t\t\t\t}
\t\t\t\t\t// 匹配城市名
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"name")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->name, item->valuestring,strlen(item->valuestring)); \t\t// 保存數據供外部調用
\t\t\t\t\t}
\t\t\t\t\t// 匹配城市所在的國家
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"country")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->country, item->valuestring,strlen(item->valuestring)); \t// 保存數據供外部調用
\t\t\t\t\t}
\t\t\t\t\t// 匹配完整地名路徑

\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"path")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->path, item->valuestring,strlen(item->valuestring)); \t\t// 保存數據供外部調用\t
\t\t\t\t\t}
\t\t\t\t\t// 匹配時區
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"timezone")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->timezone, item->valuestring,strlen(item->valuestring)); \t// 保存數據供外部調用\t
\t\t\t\t\t}
\t\t\t\t\t// 匹配時差
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"timezone_offset")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->timezone_offset, item->valuestring,strlen(item->valuestring)); \t// 保存數據供外部調用
\t\t\t\t\t}
\t\t\t\t}
\t\t\t\t/* 匹配子對象2:今天的天氣情況 */
\t\t\t\tif((subobject = cJSON_GetObjectItem(object,"now")) != NULL)
\t\t\t\t{
\t\t\t\t\t// 匹配天氣現象文字
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"text")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->text, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用
\t\t\t\t\t}
\t\t\t\t\t// 匹配天氣現象代碼
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"code")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->code, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用
\t\t\t\t\t}
\t\t\t\t\t// 匹配氣溫
\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"temperature")) != NULL)
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->temperature, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用
\t\t\t\t\t}\t
\t\t\t\t}
\t\t\t\t/* 匹配子對象3:數據更新時間(該城市的本地時間) */
\t\t\t\tif((subobject = cJSON_GetObjectItem(object,"last_update")) != NULL)
\t\t\t\t{
\t\t\t\t\tmemcpy(result->last_update, subobject->valuestring,strlen(subobject->valuestring)); // 保存數據供外部調用

\t\t\t\t}
\t\t\t}
\t\t}
\t}
\t
\tcJSON_Delete(json); //釋放cJSON_Parse()分配出來的內存空間
\t
\treturn 0;
}/<code>


(2)daily.json示例及解析:

daily.json:

「socket應用」基於C語言的TCP天氣客戶端的實現


daily.json解析函數:

<code>/*******************************************************************************************************
** 函數: cJSON_DailyWeatherParse,解析近三天天氣數據
**------------------------------------------------------------------------------------------------------
** 參數: JSON:天氣數據包 result:數據解析的結果
** 返回: void
** 公號:嵌入式大雜燴
********************************************************************************************************/
static int cJSON_DailyWeatherParse(char *JSON, Weather *result)
{
\tcJSON *json,*arrayItem,*object,*subobject,*item,*sub_child_object,*child_Item;
\t
\tjson = cJSON_Parse(JSON); //解析JSON數據包
\tif(json == NULL)\t\t //檢測JSON數據包是否存在語法上的錯誤,返回NULL表示數據包無效
\t{
\t\tprintf("Error before: [%s]\\n",cJSON_GetErrorPtr()); //打印數據包語法錯誤的位置
\t\treturn 1;
\t}
\telse
\t{
\t\tif((arrayItem = cJSON_GetObjectItem(json,"results")) != NULL); //匹配字符串"results",獲取數組內容
\t\t{
\t\t\tint size = cJSON_GetArraySize(arrayItem); //獲取數組中對象個數
#if DEBUG
\t\t\tprintf("Get Array Size: size=%d\\n",size);
#endif
\t\t\tif((object = cJSON_GetArrayItem(arrayItem,0)) != NULL)//獲取父對象內容
\t\t\t{
\t\t\t\t/* 匹配子對象1------結構體location */
\t\t\t\tif((subobject = cJSON_GetObjectItem(object,"location")) != NULL)
\t\t\t\t{

\t\t\t\t\tif((item = cJSON_GetObjectItem(subobject,"name")) != NULL) //匹配子對象1成員"name"
\t\t\t\t\t{
\t\t\t\t\t\tmemcpy(result->name, item->valuestring,strlen(item->valuestring)); \t\t// 保存數據供外部調用
\t\t\t\t\t}
\t\t\t\t}
\t\t\t\t/* 匹配子對象2------數組daily */
\t\t\t\tif((subobject = cJSON_GetObjectItem(object,"daily")) != NULL)
\t\t\t\t{
\t\t\t\t\tint sub_array_size = cJSON_GetArraySize(subobject);
#if DEBUG
\t\t\t\t\tprintf("Get Sub Array Size: sub_array_size=%d\\n",sub_array_size);
#endif
\t\t\t\t\tfor(int i = 0; i < sub_array_size; i++)
\t\t\t\t\t{
\t\t\t\t\t\tif((sub_child_object = cJSON_GetArrayItem(subobject,i))!=NULL)
\t\t\t\t\t\t{
\t\t\t\t\t\t\t// 匹配日期
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"date")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->date[i], child_Item->valuestring,strlen(child_Item->valuestring)); \t\t// 保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配白天天氣現象文字
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"text_day")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->text_day[i], child_Item->valuestring,strlen(child_Item->valuestring)); \t// 保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配白天天氣現象代碼
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"code_day")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->code_day[i], child_Item->valuestring,strlen(child_Item->valuestring)); \t// 保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配夜間天氣現象代碼
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"code_night")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->code_night[i], child_Item->valuestring,strlen(child_Item->valuestring)); // 保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配最高溫度
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"high")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->high[i], child_Item->valuestring,strlen(child_Item->valuestring)); \t\t//保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配最低溫度

\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"low")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->low[i], child_Item->valuestring,strlen(child_Item->valuestring)); \t\t// 保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配風向
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"wind_direction")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->wind_direction[i],child_Item->valuestring,strlen(child_Item->valuestring)); //保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配風速,單位km/h(當unit=c時)
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"wind_speed")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->wind_speed[i], child_Item->valuestring,strlen(child_Item->valuestring)); // 保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t// 匹配風力等級
\t\t\t\t\t\t\tif((child_Item = cJSON_GetObjectItem(sub_child_object,"wind_scale")) != NULL)
\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\tmemcpy(result->wind_scale[i], child_Item->valuestring,strlen(child_Item->valuestring)); // 保存數據
\t\t\t\t\t\t\t}
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t}
\t\t\t\t/* 匹配子對象3------最後一次更新的時間 */
\t\t\t\tif((subobject = cJSON_GetObjectItem(object,"last_update")) != NULL)
\t\t\t\t{
\t\t\t\t\t//printf("%s:%s\\n",subobject->string,subobject->valuestring);
\t\t\t\t}
\t\t\t}
\t\t}
\t}
\t
\tcJSON_Delete(json); //釋放cJSON_Parse()分配出來的內存空間
\t
\treturn 0;
}/<code>

5、獲取天氣數據並解析

這個函數就涉及到我們上一節筆記中的socket編程的知識了,先看一下這個函數實現的總體框圖:

「socket應用」基於C語言的TCP天氣客戶端的實現

下面是函數實現的細節過程:

<code>/*******************************************************************************************************
** 函數: GetWeather,獲取天氣數據並解析
**------------------------------------------------------------------------------------------------------
** 參數: weather_json:需要解析的json包 location:地名 result:數據解析的結果
** 返回: void
** 公號:嵌入式大雜燴
********************************************************************************************************/
static void GetWeather(char *weather_json, char *location, Weather *result)
{
\tSOCKET ClientSock;
\tWSADATA wd;

\tchar GetRequestBuf[256] = {0};
\tchar WeatherRecvBuf[2*1024] = {0};
\tchar GbkRecvBuf[2*1024] = {0};
\tint gbk_recv_len = 0;
\tint connect_status = 0;
\t
\t/* 初始化操作sock需要的DLL */
\tWSAStartup(MAKEWORD(2,2),&wd);
\t
\t/* 設置要訪問的服務器的信息 */
SOCKADDR_IN ServerSockAddr;
memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); \t\t // 每個字節都用0填充
ServerSockAddr.sin_family = PF_INET;\t\t\t\t\t\t // IPv4
ServerSockAddr.sin_addr.s_addr = inet_addr(WEATHER_IP_ADDR); // 心知天氣服務器IP
ServerSockAddr.sin_port = htons(WEATHER_PORT); \t\t\t // 端口
\t
\t/* 創建客戶端socket */
\tif (-1 == (ClientSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)))
\t{
\t\tprintf("socket error!\\n");
\t\texit(1);
\t}
\t
\t/* 連接服務端 */
\tif (-1 == (connect_status = connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR))))
\t{
\t\tprintf("connect error!\\n");
\t\texit(1);
\t}
\t
\t/* 組合GET請求包 */
\tsprintf(GetRequestBuf, GET_REQUEST_PACKAGE, weather_json, KEY, location);
\t
\t/* 發送數據到服務端 */
\tsend(ClientSock, GetRequestBuf, strlen(GetRequestBuf), 0);
\t\t
\t/* 接受服務端的返回數據 */
\trecv(ClientSock, WeatherRecvBuf, 2*1024, 0);
\t
\t/* utf-8轉為gbk */
\tSwitchToGbk((const unsigned char*)WeatherRecvBuf, strlen((const char*)WeatherRecvBuf), (unsigned char*)GbkRecvBuf, &gbk_recv_len);\t
#if DEBUG
\tprintf("服務端返回的數據為:%s\\n", GbkRecvBuf);

#endif
\t
\t/* 解析天氣數據並保存到結構體變量weather_data中 */
\tif (0 == strcmp(weather_json, NOW_JSON))\t\t// 天氣實況
\t{
\t\tcJSON_NowWeatherParse(GbkRecvBuf, result);\t
\t}
\telse if(0 == strcmp(weather_json, DAILY_JSON)) // 未來三天天氣
\t{
\t\tcJSON_DailyWeatherParse(GbkRecvBuf, result);\t
\t}
\t
\t/* 清空緩衝區 */
\tmemset(GetRequestBuf, 0, 256);
\tmemset(WeatherRecvBuf, 0, 2*1024);
\tmemset(GbkRecvBuf, 0, 2*1024);
\t
\t/* 關閉套接字 */
\tclosesocket(ClientSock);
\t
\t/* 終止使用 DLL */
\tWSACleanup();
}
/<code>

6、編譯

如何編譯這份代碼(可在文末進行獲取)呢?

這份C代碼工程的文件如下:

「socket應用」基於C語言的TCP天氣客戶端的實現

在windows系統下使用gcc編譯器編譯,編譯命令為:

<code>gcc weather_client.c cJSON.c utf8togbk.c -o weather_client.exe -lwsock32/<code>

如:

「socket應用」基於C語言的TCP天氣客戶端的實現

這裡的weather_client.exe就是我們編譯生成的可執行文件:天氣客戶端,雙擊就可以運行了。此外,-lwsock32參數上一節也有講過,這個參數用於鏈接windows下socket編程必須的winsock2這個庫。若是使用集成開發環境,則需要把wsock32.lib放在工程目錄下,並在我們代碼中#include <winsock2.h> 下面加上一行 #pragma comment(lib, "ws2_32.lib")代碼(在IDE裡編譯本人未驗證,有興趣的朋友可嘗試)。/<winsock2.h>


需要說明的是,Windows下默認是沒有裝gcc的,需要自己進行配置,關於mingw的配置及使用之後再做分享。

7、運行結果示例

「socket應用」基於C語言的TCP天氣客戶端的實現

此處,只能使用拼音進行搜索,其實也可以做輸入漢字進行搜索的功能,只是要進行轉碼處理。





分享到:


相關文章: