基於Linux、C、JSON、Socket的編程實例

一、前言

上一篇分享的基於控制檯的簡易天氣客戶端是基於Windows的: ,這篇分享的是基於Linux的。關於源碼有需要的朋友可私信我。

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

1、秘鑰

心知天氣:www.seniverse.com

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

私鑰 SMEieQjde1C9eXnbE

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

2、IP和端口

我們與服務端通信,需要知道三個重要的信息,分別是:

IP地址端口傳輸方式

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

<code>/* 設置要訪問的服務器的信息 */struct sockaddr_in ServerSockAddr;memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 每個字節都用0填充ServerSockAddr.sin_family = PF_INET; // IPv4ServerSockAddr.sin_addr.s_addr = inet_addr(WEATHER_IP_ADDR); // 心知天氣服務器IPServerSockAddr.sin_port = htons(WEATHER_PORT); // 端口/<code>

這裡的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

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

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

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

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

對應代碼如下:

<code>/* 秘鑰,注意!!如果要用這一份代碼,這個一定要改為自己的,因為這個我已經故意改錯了,防止有人與我公用一個KEY */#define KEY "2owqvhhd2dd9o9f8" // 這是在心知天氣註冊後,每個用戶自己的一個key​/* GET請求包 */#define GET_REQUEST_PACKAGE \\ "GET https://api.seniverse.com/v3/weather/%s.json?key=%s&location=%s&language=zh-Hans&unit=c\\r\\n\\r\\n"​/* JSON數據包 */ #define NOW_JSON "now"#define DAILY_JSON "daily"//....還用更多其他的天氣數據包可查閱心知天氣​/* 組合GET請求包 */sprintf(GetRequestBuf, GET_REQUEST_PACKAGE, weather_json, KEY, location);​/* 發送數據到服務端 */write(ClientSock, GetRequestBuf, strlen(GetRequestBuf));/<code>

這裡簡單複習一下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格式數據。我們這個天氣客戶端只是實現了查詢此刻天氣(對應的數據包為now.json)及近三天天氣情況(對應的數據包為daily.json),如要查詢其他信息,可模仿我們這裡處理now.json和daily.json的方法,我們用cJson庫進行解析。

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

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

<code>/* 天氣數據結構體 */typedef struct{ /* 實況天氣數據 */ char id[32]; //id char name[32]; //地名 char country[32]; //國家 char path[32]; //完整地名路徑 char timezone[32]; //時區 char timezone_offset[32]; //時差 char text[32]; //天氣預報文字 char code[32]; //天氣預報代碼 char temperature[32]; //氣溫 char last_update[32]; //最後一次更新的時間​ /* 今天、明天、後天天氣數據 */ char date[3][32]; //日期 char text_day[3][64]; //白天天氣現象文字 char code_day[3][32]; //白天天氣現象代碼 char code_night[3][64]; //晚間天氣現象代碼 char high[3][32]; //最高溫 char low[3][32]; //最低溫 char wind_direction[3][64]; //風向 char wind_speed[3][32]; //風速,單位km/h(當unit=c時) char wind_scale[3][32]; //風力等級}Weather;/<code>

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

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

now.json:

<code>{ "results": [ { "location": { "id": "C23NB62W20TF", "name": "西雅圖", "country": "US", "path": "西雅圖,華盛頓州,美國", "timezone": "America/Los_Angeles", "timezone_offset": "-07:00" }, "now": { "text": "多雲", //天氣現象文字 "code": "4", //天氣現象代碼 "temperature": "14", //溫度,單位為c攝氏度或f華氏度 "feels_like": "14", //體感溫度,單位為c攝氏度或f華氏度 "pressure": "1018", //氣壓,單位為mb百帕或in英寸 "humidity": "76", //相對溼度,0~100,單位為百分比 "visibility": "16.09", //能見度,單位為km公里或mi英里 "wind_direction": "西北", //風向文字 "wind_direction_degree": "340", //風向角度,範圍0~360,0為正北,90為正東,180為正南,270為正西 "wind_speed": "8.05", //風速,單位為km/h公里每小時或mph英里每小時 "wind_scale": "2", //風力等級,請參考:http://baike.baidu.com/view/465076.htm "clouds": "90", //雲量,單位%,範圍0~100,天空被雲覆蓋的百分比 #目前不支持中國城市# "dew_point": "-12" //露點溫度,請參考:http://baike.baidu.com/view/118348.htm #目前不支持中國城市# }, "last_update": "2015-09-25T22:45:00-07:00" //數據更新時間(該城市的本地時間) } ]}/<code>

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

<code>"text": "多雲", //天氣現象文字"code": "4", //天氣現象代碼"temperature": "14", //溫度,單位為c攝氏度或f華氏度/<code>

now.json的解析函數:

<code>/********************************************************************************************************* 函數: cJSON_NowWeatherParse,解析天氣實況數據**------------------------------------------------------------------------------------------------------** 參數: JSON:天氣數據包 result:數據解析的結果** 返回: void********************************************************************************************************/static int cJSON_NowWeatherParse(char *JSON, Weather *result){ cJSON *json,*arrayItem,*object,*subobject,*item; json = cJSON_Parse(JSON); //解析JSON數據包 if(json == NULL) //檢測JSON數據包是否存在語法上的錯誤,返回NULL表示數據包無效 { printf("Error before: [%s]\\n",cJSON_GetErrorPtr()); //打印數據包語法錯誤的位置 return 1; } else { if((arrayItem = cJSON_GetObjectItem(json,"results")) != NULL); //匹配字符串"results",獲取數組內容 { int size = cJSON_GetArraySize(arrayItem); //獲取數組中對象個數#if DEBUG printf("cJSON_GetArraySize: size=%d\\n",size); #endif if((object = cJSON_GetArrayItem(arrayItem,0)) != NULL)//獲取父對象內容 { /* 匹配子對象1:城市地區相關 */ if((subobject = cJSON_GetObjectItem(object,"location")) != NULL) { // 匹配id if((item = cJSON_GetObjectItem(subobject,"id")) != NULL) { memcpy(result->id, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } // 匹配城市名 if((item = cJSON_GetObjectItem(subobject,"name")) != NULL) { memcpy(result->name, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } // 匹配城市所在的國家 if((item = cJSON_GetObjectItem(subobject,"country")) != NULL) { memcpy(result->country, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } // 匹配完整地名路徑 if((item = cJSON_GetObjectItem(subobject,"path")) != NULL) { memcpy(result->path, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } // 匹配時區 if((item = cJSON_GetObjectItem(subobject,"timezone")) != NULL) { memcpy(result->timezone, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } // 匹配時差 if((item = cJSON_GetObjectItem(subobject,"timezone_offset")) != NULL) { memcpy(result->timezone_offset, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } } /* 匹配子對象2:今天的天氣情況 */ if((subobject = cJSON_GetObjectItem(object,"now")) != NULL) { // 匹配天氣現象文字 if((item = cJSON_GetObjectItem(subobject,"text")) != NULL) { memcpy(result->text, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } // 匹配天氣現象代碼 if((item = cJSON_GetObjectItem(subobject,"code")) != NULL) { memcpy(result->code, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } // 匹配氣溫 if((item = cJSON_GetObjectItem(subobject,"temperature")) != NULL) { memcpy(result->temperature, item->valuestring,strlen(item->valuestring)); // 保存數據供外部調用 } } /* 匹配子對象3:數據更新時間(該城市的本地時間) */ if((subobject = cJSON_GetObjectItem(object,"last_update")) != NULL) { memcpy(result->last_update, subobject->valuestring,strlen(subobject->valuestring)); // 保存數據供外部調用 } } } } cJSON_Delete(json); //釋放cJSON_Parse()分配出來的內存空間 return 0;}/<code>

5、獲取天氣數據並解析

實現的總體框圖(這是Window版的框圖,Linux版的也是類似這樣的思路):


6、編譯、運行

<code>gcc -std=c99 weather_client.c cJSON.c -o weather_client/<code>