使用Qt框架開發http服務器問題的記錄


最近需求需要開發一款 HTTP ,然后由於先前接觸過Qt,就直接用Qt寫HTTP服務器了,也是為了當作練手,要不然是直接上HTTP框架的。

后端用C++ Qt框架 前端為了練手 當然是純生的 js html css

 

具體的HTTP 實現過程我就不累贅描述了,這個Http協議解析基本上大部分人都知道原理。

主要是記錄一下開發中遇到的各種問題。

首先最開始開發的時候,一路順風,我的設計模式是 層次 設計模式,一層層獨立互不相干互不干涉。嚴格的只管理好自己的所在層。

數據包是一層層往上傳輸,到達 Logic 層 指令處理完畢之后 返回要顯示的數據(比如HTML),於是再一層層往下返回,一層層加報頭;

是不是有點類似於 七層協議?

由於軟件本身只是后台界面使用,所以並沒有考慮到 線程池,直接使用多線程。

在制作過程中,最經常遇到的莫過於就是編碼問題,本身應該是一個很簡單的問題,但是有時候確實出現的次數比較多。雖然說解決也簡單。

首先我們統一編碼。內部程序和源代碼和html文件均為 UTF-8。

在開發到 60%,也就是在設計 身份識別的地方,我們想了一個辦法,為了保證其安全性,我們用了一直理論上我們沒有找到什么缺陷的方法:

 
根據一段時間的討論,在不考慮Cookie被盜取(Cookie是以會話的形式存在)的情況下,似乎沒有發現什么問題。

以下為 Cookie 生成算法:

復制代碼
QString Cookie::getRandCookie(QString & name, QString & pass){
    QByteArray bb;
    QString temp;
    QString md5_pass;
    QString randcookie;
    bb = QCryptographicHash::hash(name.toUtf8(), QCryptographicHash::Md5);
    temp = bb.toHex();
    bb = QCryptographicHash::hash(temp.toUtf8(), QCryptographicHash::Md5);
    randcookie = bb.toHex();
    bb = QCryptographicHash::hash(pass.toUtf8(), QCryptographicHash::Md5);
    temp = bb.toHex();
    bb = QCryptographicHash::hash(temp.toUtf8(), QCryptographicHash::Md5);
    md5_pass = bb.toHex();
    QTime t;
    t = QTime::currentTime();
    qsrand(t.msec() + t.second() * 65535 / 2);
    int n = qrand();
    QString tmp = QString::number(n, 1000);
    bb = QCryptographicHash::hash(tmp.toUtf8(), QCryptographicHash::Md5);
    tmp = bb.toHex();
    randcookie = randcookie + tmp + md5_pass;
    //目前總共有 32 位,為了防止用戶Cookie被XSS以免 碰撞機碰撞。 所以將返回不完整的Md5
    //     6~28位是 用戶名 33位~64位是隨機數無用
    QString userhead = randcookie.mid(6, 28);        //取用戶名前面這一段   不完整
    QString rand = randcookie.mid(33,97);   //隨機數
    QString passhead = randcookie.mid(72, 20);    //這里去掉前8位    //取中間這一段   不給完整的 MD5碼
    randcookie = rand+ userhead + passhead;//隨機數 賬號 密碼
    randcookie += "_Hi_hacker";       //向大牛打聲招呼
    return randcookie;
}
復制代碼

如果要是再看的你發現了漏洞,一定要留言告訴我,我將立即改進。

隨后我們遇到了編碼問題。

一個問題就是,因為我們的需求包括了 網頁操作控制台(用匿名管道實現,詳情可以看我的這篇文章:http://www.cnblogs.com/suwings/p/5754943.html,順帶一提,Qt框架也可以實現,只是由於時間問題,沒法再做描述)

但是Windows 控制台默認是 GBK 編碼,這將導致一個問題的出現,中文輸入的數據可能將亂碼。輸出的數據也可能將亂碼。

不過在我們測試的發現居然忘記URL中文解碼了,但是Qt自帶的解碼有個問題就是 英文有時候也會一起解碼。

於是在網上找到了如下實現方法:

復制代碼
 1 /************************************************************************/
 2 /* URL解碼                          英文可不解                                             */
 3 /************************************************************************/
 4 std::string urlDecode(const std::string & _szToDecode)
 5 {
 6     std::string result;
 7     int hex = 0;
 8     for (size_t i = 0; i < _szToDecode.length(); ++i)
 9     {
10         switch (_szToDecode[i])
11         {
12         case '+':
13             result += ' ';
14             break;
15         case '%':
16             if (isxdigit(_szToDecode[i + 1]) && isxdigit(_szToDecode[i + 2]))
17             {
18                 std::string hexStr = _szToDecode.substr(i + 1, 2);
19                 hex = strtol(hexStr.c_str(), 0, 16);
20                 //字母和數字[0-9a-zA-Z]、一些特殊符號[$-_.+!*'(),] 、以及某些保留字[$&+,/:;=?@]  
21                 //可以不經過編碼直接用於URL  
22                 if (!((hex >= 48 && hex <= 57) || //0-9  
23                     (hex >= 97 && hex <= 122) ||   //a-z  
24                     (hex >= 65 && hex <= 90) ||    //A-Z  
25                     hex == 0x21 || hex == 0x24 || hex == 0x26 || hex == 0x27 || hex == 0x28 || hex == 0x29
26                     || hex == 0x2a || hex == 0x2b || hex == 0x2c || hex == 0x2d || hex == 0x2e || hex == 0x2f
27                     || hex == 0x3A || hex == 0x3B || hex == 0x3D || hex == 0x3f || hex == 0x40 || hex == 0x5f
28                     ////一些特殊符號及保留字[$-_.+!*'(),]  [$&+,/:;=?@]   
29                     ))
30                 {
31                     result += char(hex);
32                     i += 2;
33                 }else if{esult += '%';}else{result += '%';}
34             break;
35         default:
36             result += _szToDecode[i];
37             break;
38         }
39     }
40     return result;
41 }
復制代碼

 

於是很開心的完成了URL解碼,開始專注 到控制台的編碼問題:

從 QString UTF-8 轉到 Windows cmd GBK:

復制代碼
 1 string UTF8ToGBK(const char* strUTF8)
 2 {
 3     int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0);
 4     wchar_t* wszGBK = new wchar_t[len + 1];
 5     memset(wszGBK, 0, len * 2 + 2);
 6     MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len);
 7     len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
 8     char* szGBK = new char[len + 1];
 9     memset(szGBK, 0, len + 1);
10     WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
11     string strTemp(szGBK);
12     if (wszGBK) delete[] wszGBK;
13     if (szGBK) delete[] szGBK;
14     return strTemp;
15 }
復制代碼

以及輸出: 從 Windows CMD GBK 轉回 UTF-8:

復制代碼
 1 void Pipe::loop(){
 2     char outbuff[4096];        //輸出緩沖
 3     DWORD byteread;
 4     while (true)
 5     {
 6         memset(outbuff, '\0', 4096);
 7         if (ReadFile(this->hpiperead, outbuff, 4095, &byteread, NULL) == NULL)break;
 8 
 9         QTextCodec *gbk1 = QTextCodec::codecForName("GBK");        //Windows 默認編碼 GBK 轉成 UTF-8 //主要是看這里 
10         QString tmp = gbk1->toUnicode(outbuff);                    //主要是看這里
11         while(tmp.indexOf('\b') != -1)tmp.replace('\b',"");        //替換管道可能出現的亂碼
12      //這樣 Qstring tmp 就可以使用了。
13         memset(outbuff, '\0', 4096);
14     }
15 }
復制代碼

然后差不多幾個重點的問題解決了。於是繼續愉快的code

但是好景不長,后來發現返回的數據在 HTTP 響應頭里面總是 少了,也就是說 四個漢字 “啊啊啊啊” 變成了 “啊啊”;

我原先一直以為是編碼問題,在TCP層我多層換編碼輸出,用UTF-8,GBK gb等等一些編碼。都無果。

后來發現 是一句代碼坑了這里:

復制代碼
    //------處理數據長度--------
    QString tmp_read_len;
    //int i;  用前面用過的i,無需要重新那個
    //Body 是QList類型
    i = 0;
    for (line = 0; line < body.size(); line++)    //這個循環是 循環加入 從上層返回的數據
    {
        i = i + body[line].toLocal8Bit().length();  
        //字節數,判斷中文/英文 就是因為少了toLocal8Bit,所以導致中文判斷也以為是一個,實際上可能是 2 個或 4個 (UTF-8)
    }
    QString tmp_int_len = QString::number(i);
    QString  ContentLen = "Content-Length: " + tmp_int_len;    //加入 Content-Length 
    (*list) << ContentLen;
復制代碼

 

於是解決之后,網頁終於能顯示“啊啊啊啊”了,於是又開始愉快的code。

可惜好景不長,在一個及其簡單的地方,出現了差錯。我需要實現一個 在控制台也可以操作的需求,這個很簡單,用一個線程專門讀取用戶輸入就好了

對...是很簡單

復制代碼
 1 //循環等待輸入
 2 void LoopCin(){
 3     std::string com;
 4     while (true){
 5         char ch = '\0';
 6         ch = getchar();
 7         if (ch == '\n'){
 8                 //考慮多一點
 9             if (PIPE != NULL){    //PIPE 是管道
10                 std::cout << ">>" << com.c_str()<< std::endl;
11                 PIPE->sendCommand(com.c_str());        //向管道發送命令 管道已經是封裝好了的
12                 com = "";
13             }else{
14                 std::cout << "[程序]" << "服務器未開啟,無法執行命令.請去網頁后台開啟您的服務器."<< std::endl;
15             }
16         }
17         else
18         {
19             com = com + ch;        //如果不是回車 就加入char
20         }
21     }
22 }
復制代碼

於是寫完這些代碼之后,用C++11 的Thread 類創建線程(別問我為什么不用QThread 類,為了實現一個這個還用着那個,而且據說這個使用起來需要謹慎)

可是 用Thread創建的線程卻 毫無效果,明明可以等待用戶輸入了,卻將主線程個阻塞了,很是詫異。。

我也不是專門走C++這條路的,所以沒有詳細的去調查為什么,於是我用代替方法,直接使用了 WIndows API 創建線程。

1  PIPE_cin_hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)LoopCin, NULL, 0, &PIPE_cin_ThreadID);//創建輸入循環線程

奇跡般的不知道為什么的突然就可以了。莫非 Thread 創建的線程不可靠?不是沒有啟動,確實啟動了,但是卻阻塞了主線程,整個進程在等待我輸入,網頁也加載不出來了。

如果你知道這個原理,還煩請告訴我一下,謝謝。

 

於是又開始愉快的進行code。

雖然后面還有點小插曲,但是都一一解決,完成了這個項目。關於Javascript 編寫那里遇到的坑其實也沒多少,就不寫了。

 

不論是否對你有幫助,謝謝你的耐心閱讀


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM