參考鏈接 :
http chunked : http://blog.csdn.net/yaneng/article/details/4378984
zlib壓縮和解壓gzip : http://www.oschina.net/code/snippet_65636_22542
請多多支持以上作者,謝謝...
前幾天寫了這個主題,但是那個時候思路不清晰,寫的亂七八糟.寫博客爲的就是理清思路.並且程序的bug也有很多,實在誤人誤己,所以刪掉之前的那篇,從新寫一篇.
遺憾上傳的附件沒有辦法刪除.
背景
linux下並沒有什麼比較牛的翻譯軟件,之前youdao倒是做了一個,但是有個細節處理的不是很好,正好擋住vim的狀態欄最右邊的一部分,導致組合命令用起來很不方便,就放棄了.但是最近總是看英文文檔,對翻譯軟件的需求還是有的.所以就萌生了一個自己做一個的想法.用了一個星期,總算初見成效.
設計思路
1. 解析域名連接服務器.
這一步比較簡單,調用幾個API就可以了.
代碼片段 :
// 域名對應IP
struct hostent *ent;
ent = gethostbyname(host);
if (NULL == ent || NULL == *ent->h_addr_list)
{
perror("\n");
return -1;
}
// 非阻塞連接服務器
int ret = 0;
int flag = 0;
int sockfd = 0;
struct sockaddr_in addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
perror("\n");
return -1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
addr.sin_addr.s_addr = naddr;
flag = fcntl(sockfd, F_GETFL, 0);
ret = fcntl(sockfd, F_SETFL, flag | O_NONBLOCK);
if (ret < 0)
{
close(sockfd);
return -1;
}
ret = connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));
if (ret < 0)
{
if (errno != EINPROGRESS)
{
close(sockfd);
perror("\n");
return -1;
}
}
else if (ret == 0)
{
goto DONE;
}
fd_set wset;
fd_set tmpset;
struct timeval tv;
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
tv.tv_sec = 10;
tv.tv_usec = 0;
while (1)
{
tmpset = wset;
ret = select(sockfd + 1, NULL, &tmpset, NULL, &tv);
if (ret < 0)
{
if (EINTR == errno)
{
continue;
}
else
{
close(sockfd);
perror("\n");
return -1;
}
}
else if (0 == ret)
{
close(sockfd);
return -1;
}
break;
}
DONE:
return sockfd;
2. 構造消息發送請求.
我並不知道該給有道發什麼消息,所以就在網頁上面翻譯了一個單詞,通過wireshark查看,去掉cookie等信息如下:
POST /translate?smartresult=dict&smartresult=rule&smartresult=ugc&sessionFrom=null HTTP/1.1\r\n\
Host: fanyi.youdao.com\r\n\
Connection: keep-alive\r\n\
Content-Length: %d\r\n\
Accept: application/json, text/javascript, */*; q=0.01\r\n\
Origin: http://fanyi.youdao.com\r\n\
X-Requested-With: XMLHttpRequest\r\n\
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36\r\n\
Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n\
Referer: http://fanyi.youdao.com/\r\n\
Accept-Encoding: gzip, deflate\r\n\
Accept-Language: en-US,en;q=0.8\r\n\
\r\n\
type=AUTO&i=%s&doctype=json&xmlVersion=1.8&keyfrom=fanyi.web&ue=UTF-8&action=FY_BY_CLICKBUTTON&typoResult=true";
分析一下這個請求可以發現
a. 服務器給我們返回的是json的格式.我們得支持gzip壓縮.
b. i=%s : 這裏就是我們要存放翻譯原文的地方.如果是英文,直接原文即可,但是對於漢字,這裏的處理有點特別.一箇中文的utf-8是三個字節,但是有道的請求裏面,一箇中文佔六個字節,比如快樂 :正常的utf-8編碼應該是 %E5%BF%AB%E4%B9%90 , 但是有道里面是這個樣子的%C3%A5%C2%BF%C2%AB%C3%A4%C2%B9%C2%90 .網上搜了很久都沒有找到問題的原因,後來FQ用谷歌找到了問題的答案,原來是因爲js的一個函數EncodeURI,這個函數會把漢字給轉成6個字節.對應表在代碼的encoding.h裏面,
c. 其實這裏我被瀏覽器坑了,Accept-Encoding這個,如果不加,有道會返回一個非gzip壓縮的明文格式,結果我剛開始不知道,以爲是必須的,然後就有了各種糾結解壓縮.不過好處當然也顯而易見,對zlib又有了新的理解.d. 另外就是有道也在這裏坑了一把,明明我有keep-alive,結果它並沒有,每次請求完,它立刻關閉連接.導致我下一次請求直接RST了.這個異常的處理方式是,如果write失敗,就退出.
參考unp,寫一個關閉的套結字會收到RST,如果不管這個RST繼續寫入,程序就會崩潰,因爲收到了SIGPIPE,如果不想被異常終止,需要捕獲SIGPIPE,或者忽略它.
3. 接收應答,解出header和body
這裏很明顯我的設計存在一個bug的,我只固定讀取了給定大小.因爲我的場景不會有大篇幅的翻譯.
如果說需要大篇幅,那麼正確的做法應該是申請一個大buffer,接收數據,根據chunked判斷有沒有到達消息尾部,如果沒有到達而buffer滿了,先追加到某個文件裏面,繼續讀...
4. 解chunk消息.得到壓縮的應答正文.
關於chunked,文首的連接想必說的很清楚了,明確chunked格式,還是不難解的,比較坑爹的地方是,每一段chunked長度是用ascii碼保存的.我開始理解錯了,以爲是直接轉成數字就可用,結果走了一段彎路.參考代碼片段 :
int i = 0;
int len = 0;
int power = 0;
int dec = 0;
int seed = 0;
len = strlen(hex);
for (i = len - 1; i >= 0; i--)
{
power = len - i - 1;
switch (hex[i])
{
case '0':
{
seed = 0;
}
break;
case '1':
{
seed = 1;
}
break;
case '2':
{
seed = 2;
}
break;
case '3':
{
seed = 3;
}
break;
case '4':
{
seed = 4;
}
break;
case '5':
{
seed = 5;
}
break;
case '6':
{
seed = 6;
}
break;
case '7':
{
seed = 7;
}
break;
case '8':
{
seed = 8;
}
break;
case '9':
{
seed = 9;
}
break;
case 'a':
{
seed = 10;
}
break;
case 'b':
{
seed = 11;
}
break;
case 'c':
{
seed = 12;
}
break;
case 'd':
{
seed = 13;
}
break;
case 'e':
{
seed = 14;
}
break;
case 'f':
{
seed = 15;
}
break;
default:
break;
}
dec += seed * pow(16, power);
}
return dec;
5. 解壓,得到json字符串.
關於解壓縮,我就不過多描述了,參考我的另一片文章,借鑑了很多網友的成果,還是有一定參考性的.
http://blog.csdn.net/cp3alai/article/details/51282338
6. 解析json字符串.
網上參考了很多的博文,結果都看不懂,實在是因爲我對json也沒那麼瞭解,所以就首先對json做了一下入門,參考 : http://www.w3school.com.cn/json/解析工具選擇了cJSON, 源碼路徑 : https://github.com/DaveGamble/cJSON.git .這裏面的例子 test.c 很清晰易懂.
我本來想再封裝一層,無奈又是object又是數組,需要調用的方法又不一樣,失敗.只能傻瓜一樣,按順序找到我需要的解釋.
需要完善的地方:
1. 接收數據的緩衝區寫死了,如果需要翻譯的數據足夠大,可能就完蛋了.
2. html是chunked的格式,但是我只解了第一個chunk,如果需要翻譯的數據足夠大,還是會有問題的.
使用如圖 :
更新了以後,穩定多了,原來上傳的那個沒有辦法刪除,只能從新再傳一個了...
http://download.csdn.net/detail/cp3alai/9509158