淺談jsoncpp及實現伺服器客戶端之間json包傳輸
JSON是什麼?
JSON 是一種用於資料交換的文字格式(本質是一種檔案組織方式,比如你熟悉的txt, csv, doc,docx檔案等等),目的是取代繁瑣笨重的XML格式。這種格式不僅人很容易進行閱讀和編寫,同時機器也很容易解析和生成,是當前十分流行的資料格式,尤其是在前端領域。JSON是一種傳遞物件的語法,物件可以是name/value對、陣列和其他物件,現在瀏覽器都自帶json解析。
簡單來說json就是一種客戶端與伺服器都可以識別及解析的資料格式,避免了難以定義資料格式的麻煩。比如:如果你每個客戶端想給伺服器一次傳送名稱,年齡,密碼等資訊,那麼伺服器用什麼資料結構來接收呢?有人就說用結構體,但是結構體中各個變數我們定義多大呢?是吧,多大都不合適,所以有了json這種統一的資料格式,雙方都根據json的規則進行解析就可以獲取到雙方傳送的位元組流(網路中用位元組流傳輸),而不必為定義每個資料的大小而苦惱(相當於傳送接收都是一個json物件不存在大小不一致)。可以將json理解為一個容器,我們按照容器的開啟方式就可以開啟容器獲取到資料。
舉個栗子(下面提供一小段json程式碼)?
{ "firstname":"John", "lastname":"smith", "isAlive":true, "age":25, "address":{ "streetaddress":"21 street", //街道地址 "city":"XI'AN", //城市地址 }, "phonenum":{ { "type":"home", //家庭電話 "number": "212555666" }, { "type":"office", //公司電話 "number":"645111999" } }, "children":{ [{"name":"mary","age",2},{"name":"bob","age":4}] //陣列 }, "spouse":null }
這是個json,儲存的資料描述了一個人John Smith的一些個人資訊,比如姓名,是否活著,年齡,地址以及電話號碼等資訊。其中,地址address和電話號碼phonenum呢,下面又包含了街道、城市,家庭電話、辦公電話資訊。
如果之前只接觸過儲存在Excel或者sql server等結構化資料庫中的結構化資料,那麼第一次看到json格式的資料,一定覺得很新穎。這種資料組織方式,條理清晰,能更好的表示自然界中人或物等實體的屬性關係。可以理解為一種樹狀結構,一棵有枝幹,枝幹上有葉子的樹。
你看懂了吧?那麼當你再看到它時就知道它是JSON了。主要部分:
花括弧,方括號,冒號和逗號
- 花括弧表示一個“容器”
- 方括號裝載陣列
- 名稱和值用冒號隔開
- 陣列元素通過逗號隔開
區分json和XML?
<1>什麼是XML?
XML也是一種格式規範,是一種包含了資料以及資料說明的文字格式規範,它是可擴充套件標記語言。擴充套件標記語言不是超文字標記語言(eg:HTML)的替代而是對超文字標記語言的補充。XML不像HTML這種超文字標記語言用於網頁的編輯(被設計用來顯示資料,其焦點是資料的外觀),它主要用於資料格式化儲存(被設計用來傳輸和儲存資料,其焦點是資料的內容),現在用的比較多的是作為配置檔案(表述底層資料)或者資料結構定義(資料載體),在網路中傳輸,是網路傳輸中間語言。
舉個栗子說明一下:
比如,我們要給對方傳輸一段資料,資料內容是“too young,too simple,sometimes naive”,要將這段話按照屬性拆分為三個資料的話,就是,年齡too young,閱歷too simple,結果sometimes naive。
我們都知道程式不像人,可以體會字面意思,並自動拆分出資料,因此,我們需要幫助程式做拆分,因此出現了各種各樣的資料格式以及拆分方式。
比如,可以是這樣的
資料為“too young,too simple,sometimes naive”
然後按照逗號拆分,第一部分為年齡,第二部分為閱歷,第三部分為結果。這種方式可以用來容納資料並能夠被解析,但是不直觀,通用性也不好。基於這種情況,出現了xml這種資料格式, 上面的資料用XML表示的話
可以是這樣<person age="too young" experience="too simple" result="sometimes naive" />
也可以是這樣
<person> <age value="too young" /> <experience value="too simple" /> <result value="sometimes naive" /> </person>
兩種方式都是xml,都很直觀,附帶了對資料的說明,並且具備通用的格式規範可以讓程式做解析。
與普通的純文字儲存資料不同的是XML與json做了資料的拆分和歸類,以便讓計算程式區分各部分的內容
拆分方式有多種,可以這樣來表述:
- 用XML格式拆分資料
- 用JSON格式拆分資料
xml可用於儲存,傳輸,交換資料,因為只描述/關心資料的結構,可以做到平臺無關,實現跨平臺,所以Windows平臺喜歡,Linux平臺喜歡,各種系統(包括非作業系統)都喜歡用它,它可拓展性好,對人類友好,結構清晰易讀。
小知識:
記得好像據說,最早是在全球資訊網剛出現的時候,為了在全球資訊網上傳輸資料,接收方能造理解,人們創了這種自帶格式說明的檔案格式。當時人們期待著xml能一統天下,結果後來html和瀏覽器出現了,html成了標準的網路協議,就沒xml什麼事了。現在xml就是個工具,幹啥都行。可以儲存並檢索資料,可以做傳輸約定格式,可以做配置檔案。
<2>json與xml的相同點及json比xml好在哪裡?
首先XML和JSON都使用結構化方法來標記資料,而且它們的值都是可列舉的,是“人類可讀”的,兩者都可以巢狀(有層級的,可以在值裡再存放值)),而且都能被多種的程式語言解析和使用,都能使用AJAX方法來傳遞(例如httpWebRequest) 。
但是JSON 簡單的語法格式和清晰的層次結構明顯要比 XML 容易閱讀,並且在資料交換方面,由於 JSON 所使用的字元要比 XML 少得多(相比 XML 檔案更小,相比XML確實更加輕量級),可以大大得節約傳輸資料所佔用得頻寬。XML有時候描述東西太費勁,驗證複雜,而且有大量的冗餘,尤其是結束標記(</html>)。如果只是幾個元素沒什麼,如果非常多,就不利於網路傳輸了,所以現在網路上用的非常多的是JSON。但是XML作為半結構化文件的代表性標準,早期代表的是一種資料思想。另外JSON的結構更容易對映至一般語言的資料結構,而且json具有長時間的穩定性(JSON 格式的創始人聲稱此格式永遠不升級),並且javaScript天生支援Json,解析一點都不費勁
XML相比JSON最大的區別是充滿了冗餘資訊。多數時候我們不需要冗餘資訊,但是一旦需要的時候沒有就是不行。這就是XML與JSON最大的區別。為什麼很多人都說json比較好,因為多數時候不需要冗餘資訊。
<3>為啥有了json還需要使用xml?
我們在選擇用哪種方式儲存資料一般會考慮以下兩點
- 哪種更容易閱讀
- 哪種更容易被程式解析(正反序列化)
根據不同的需求我們才會選擇儲存方式,只是json比較簡單,容易閱讀而已,但是在某些場合,在人機都需要識別資料的時候,比如配置檔案,選擇xml正是因為有大量的冗餘(提示資訊)使得xml的可讀性比json好。所以說我們選擇那種方式取決於我們的需求,這兩者不是互相替代的
看個XML和json的栗子
XML:
<?xml version="1.0" encoding="utf-8">
<country>
<name>中國</name>
<province>
<name>廣東</name>
<cities>
<city>廣州</city>
<city>佛山</city>
<city>深圳</city>
</cities>
</province>
<province>
<name>陝西</name>
<cities>
<city>西安</city>
<city>渭南</city>
<city>咸陽</city>
</cities>
</province>
</country>
json:
{
"name":"中國",
"province":[
{
"name":"廣東",
"cities":
{
"city":["廣州","佛山","深圳"]
}
},
{
"name":"陝西",
"cities":
{
"city":["西安","渭南","咸陽"]
}
}]
}
看出來沒,其實資料都是一樣的,不同的只是資料的格式而已,同樣的資料,我用xml格式傳給你,你用xml格式解析出資料,用json格式傳給你,你就用json格式解析出資料,還可以我本地儲存的是xml格式的資料,我自己先解析出資料,然後構造成json格式傳給你,你解析json格式,獲得資料,再自己構造成xml格式儲存起來,說白了,不管是xml還是json,都只是包裝資料的不同格式而已,重要的是其中含有的資料,而不是包裝的格式。
json用法:
關於json用法大家可以參考這兩個部落格:
客戶端與伺服器之間json包傳輸簡單示例程式碼
伺服器:
#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<errno.h>
#include<json/json.h>
#include<sys/types.h>
#include<sys/socket.h>
//json連結的時候要加上 -ljson庫
using namespace std;
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
cout<<"socket fail;errno:"<<errno<<endl;
return 0;
}
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(5500);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
if(-1==bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)))
{
cerr<<"bind fail;errno:"<<errno<<endl; //cerr
return 0;
}
if(-1==listen(sockfd,20))
{
cout<<"listen fail;errno:"<<errno<<endl;
return 0;
}
socklen_t len=sizeof(cli);
int clifd=accept(sockfd,(struct sockaddr*)&cli,&len);
if(-1==clifd)
{
cout<<"accept fail;errno:"<<errno<<endl;
return 0;
}
char buff[128]={0};
int n=recv(clifd,buff,127,0);//從網路中讀取到字元流
if(n>0)
{
//解析客戶端傳送的json包,json包含三種類型的類:Value Reader Writer
Json::Value val; //Value是jsoncpp中最基本及重要的類,表示各種型別的物件,val是定義了臨時物件,供下面程式碼用
Json::Reader read;//用於讀取,將字串轉化為Json::Value物件
if(-1==read.parse(buff,val)) //將buff轉化為Json::Value物件
{
cout<<"change json fail;errno:"<<errno<<endl;
return 0;
}
cout<<val.toStyledString()<<endl;
//cout<<"name:"<<val["name"].asString()<<endl; //asString將遠端的結果轉化為字串
//cout<<"pw:"<<val["pw"].asString()<<endl;
//給客戶端傳送訊息
Json::Value root; //root表示整個json物件
root["reason"]="ok"; //新建一個key(名為reason)賦予字串值ok
if(-1==send(clifd,root.toStyledString().c_str(),strlen(root.toStyledString().c_str()),0)) //將json物件轉化為字串在網路中傳輸
{
cout<<"send reason fail;errno:"<<errno<<endl;
return 0;
}
}
else
{
cout<<"recv fail;errno:"<<errno<<endl;
return 0;
}
return 0;
}
客戶端:
#include<iostream>
#include<string.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<errno.h>
#include<json/json.h>
#include<sys/types.h>
#include<sys/socket.h>
using namespace std;
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
cout<<"socket fail;errno:"<<errno<<endl;
return 0;
}
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(5500);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
if(-1==connect(sockfd,(struct sockaddr*)&ser,sizeof(ser)))
{
cout<<"clifd connect fail;errno:"<<errno<<endl;
return 0;
}
//建立json包
Json::Value val;
val["name"]="zhangsan";//新建key為name 值為zhangsan的val物件中的一個對映
val["pw"]="123456";
//傳送資料
if(-1==send(sockfd,val.toStyledString().c_str(),strlen(val.toStyledString().c_str()),0))
{
cout<<"clifd send fail;errno:"<<errno<<endl;
return 0;
}
//接受資料
char buff[128]={0};
if(recv(sockfd,buff,127,0)<=0)
{
cout<<"clifd recv fail;errno:"<<errno<<endl;
return 0;
}
Json::Value root;
Json::Reader read;
if(-1==read.parse(buff,root)) //字串轉化為json物件
{
cout<<"json parse fail;errno:"<<errno<<endl;
return 0;
}
//輸出json
//cout<<root.asString()<<endl;
cout<<"reason:"<<root["reason"].asString()<<endl; //按字串列印
return 0;
}
編譯連結時需要加上json庫
執行結果:
伺服器輸出時還有一種將資料分開的方式,我上面引起來了,大家可以自己放開測試一下。