簡單有道詞典客戶端C實現

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/cp3alai/article/details/51306850

參考鏈接 :

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章