首页 > 代码库 > Qt 框架 开发HTTP 服务器 开发记录
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 == 0x2926 || hex == 0x2a || hex == 0x2b || hex == 0x2c || hex == 0x2d || hex == 0x2e || hex == 0x2f27 || hex == 0x3A || hex == 0x3B || hex == 0x3D || hex == 0x3f || hex == 0x40 || hex == 0x5f28 ////一些特殊符号及保留字[$-_.+!*‘(),] [$&+,/:;=?@] 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 else18 {19 com = com + ch; //如果不是回车 就加入char20 }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 编写那里遇到的坑其实也没多少,就不写了。
不论是否对你有帮助,谢谢你的耐心阅读。
Qt 框架 开发HTTP 服务器 开发记录