最近需求需要开发一款 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() * / );
int n = qrand();
QString tmp = QString::number(n, );
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(, ); //取用户名前面这一段 不完整
QString rand = randcookie.mid(,); //随机数
QString passhead = randcookie.mid(, ); //这里去掉前8位 //取中间这一段 不给完整的 MD5码
randcookie = rand+ userhead + passhead;//随机数 账号 密码
randcookie += "_Hi_hacker"; //向大牛打声招呼
return randcookie;
}

如果要是再看的你发现了漏洞,一定要留言告诉我,我将立即改进。

随后我们遇到了编码问题。

一个问题就是,因为我们的需求包括了 网页操作控制台(用匿名管道实现,详情可以看我的这篇文章:http://www.cnblogs.com/suwings/p/5754943.html,顺带一提,Qt框架也可以实现,只是由于时间问题,没法再做描述)

但是Windows 控制台默认是 GBK 编码,这将导致一个问题的出现,中文输入的数据可能将乱码。输出的数据也可能将乱码。

不过在我们测试的发现居然忘记URL中文解码了,但是Qt自带的解码有个问题就是 英文有时候也会一起解码。

于是在网上找到了如下实现方法:

 /************************************************************************/
/* URL解码 英文可不解 */
/************************************************************************/
std::string urlDecode(const std::string & _szToDecode)
{
std::string result;
int hex = ;
for (size_t i = ; i < _szToDecode.length(); ++i)
{
switch (_szToDecode[i])
{
case '+':
result += ' ';
break;
case '%':
if (isxdigit(_szToDecode[i + ]) && isxdigit(_szToDecode[i + ]))
{
std::string hexStr = _szToDecode.substr(i + , );
hex = strtol(hexStr.c_str(), , );
//字母和数字[0-9a-zA-Z]、一些特殊符号[$-_.+!*'(),] 、以及某些保留字[$&+,/:;=?@]
//可以不经过编码直接用于URL
if (!((hex >= && hex <= ) || //0-9
(hex >= && hex <= ) || //a-z
(hex >= && hex <= ) || //A-Z
hex == 0x21 || hex == 0x24 || hex == 0x26 || hex == 0x27 || hex == 0x28 || hex == 0x29
|| hex == 0x2a || hex == 0x2b || hex == 0x2c || hex == 0x2d || hex == 0x2e || hex == 0x2f
|| hex == 0x3A || hex == 0x3B || hex == 0x3D || hex == 0x3f || hex == 0x40 || hex == 0x5f
////一些特殊符号及保留字[$-_.+!*'(),] [$&+,/:;=?@]
))
{
result += char(hex);
i += ;
}else if{esult += '%';}else{result += '%';}
break;
default:
result += _szToDecode[i];
break;
}
}
return result;
}

于是很开心的完成了URL解码,开始专注 到控制台的编码问题:

从 QString UTF-8 转到 Windows cmd GBK:

 string UTF8ToGBK(const char* strUTF8)
{
int len = MultiByteToWideChar(CP_UTF8, , strUTF8, -, NULL, );
wchar_t* wszGBK = new wchar_t[len + ];
memset(wszGBK, , len * + );
MultiByteToWideChar(CP_UTF8, , strUTF8, -, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, , wszGBK, -, NULL, , NULL, NULL);
char* szGBK = new char[len + ];
memset(szGBK, , len + );
WideCharToMultiByte(CP_ACP, , wszGBK, -, szGBK, len, NULL, NULL);
string strTemp(szGBK);
if (wszGBK) delete[] wszGBK;
if (szGBK) delete[] szGBK;
return strTemp;
}

以及输出: 从 Windows CMD GBK 转回 UTF-8:

 void Pipe::loop(){
char outbuff[]; //输出缓冲
DWORD byteread;
while (true)
{
memset(outbuff, '\0', );
if (ReadFile(this->hpiperead, outbuff, , &byteread, NULL) == NULL)break; QTextCodec *gbk1 = QTextCodec::codecForName("GBK"); //Windows 默认编码 GBK 转成 UTF-8 //主要是看这里
QString tmp = gbk1->toUnicode(outbuff); //主要是看这里
while(tmp.indexOf('\b') != -)tmp.replace('\b',""); //替换管道可能出现的乱码
     //这样 Qstring tmp 就可以使用了。
memset(outbuff, '\0', );
}
}

然后差不多几个重点的问题解决了。于是继续愉快的code

但是好景不长,后来发现返回的数据在 HTTP 响应头里面总是 少了,也就是说 四个汉字 “啊啊啊啊” 变成了 “啊啊”;

我原先一直以为是编码问题,在TCP层我多层换编码输出,用UTF-8,GBK gb等等一些编码。都无果。

后来发现 是一句代码坑了这里:

    //------处理数据长度--------
QString tmp_read_len;
//int i; 用前面用过的i,无需要重新那个
//Body 是QList类型
i = ;
for (line = ; 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。

可惜好景不长,在一个及其简单的地方,出现了差错。我需要实现一个 在控制台也可以操作的需求,这个很简单,用一个线程专门读取用户输入就好了

对...是很简单

 //循环等待输入
void LoopCin(){
std::string com;
while (true){
char ch = '\0';
ch = getchar();
if (ch == '\n'){
//考虑多一点
if (PIPE != NULL){ //PIPE 是管道
std::cout << ">>" << com.c_str()<< std::endl;
PIPE->sendCommand(com.c_str()); //向管道发送命令 管道已经是封装好了的
com = "";
}else{
std::cout << "[程序]" << "服务器未开启,无法执行命令.请去网页后台开启您的服务器."<< std::endl;
}
}
else
{
com = com + ch; //如果不是回车 就加入char
}
}
}

于是写完这些代码之后,用C++11 的Thread 类创建线程(别问我为什么不用QThread 类,为了实现一个这个还用着那个,而且据说这个使用起来需要谨慎)

可是 用Thread创建的线程却 毫无效果,明明可以等待用户输入了,却将主线程个阻塞了,很是诧异。。

我也不是专门走C++这条路的,所以没有详细的去调查为什么,于是我用代替方法,直接使用了 WIndows API 创建线程。

  PIPE_cin_hThread = CreateThread(NULL, , (LPTHREAD_START_ROUTINE)LoopCin, NULL, , &PIPE_cin_ThreadID);//创建输入循环线程

奇迹般的不知道为什么的突然就可以了。莫非 Thread 创建的线程不可靠?不是没有启动,确实启动了,但是却阻塞了主线程,整个进程在等待我输入,网页也加载不出来了。

如果你知道这个原理,还烦请告诉我一下,谢谢。

于是又开始愉快的进行code。

虽然后面还有点小插曲,但是都一一解决,完成了这个项目。关于Javascript 编写那里遇到的坑其实也没多少,就不写了。

不论是否对你有帮助,谢谢你的耐心阅读。

最新文章

  1. 【poj2096】 Collecting Bugs
  2. 手机端页面自适应之rem布局
  3. 简单C#、asp.net mvc验证码的实现
  4. 10 件有关 JavaScript 让人费解的事情
  5. log4net 配置
  6. 关于java8 interface的default方法
  7. 骨骼动画的实现(OpenGL实现)
  8. Creating Custom Connector Sending Claims with SharePoint 2013
  9. JS中exec函数与match函数的区别与联系
  10. WAF
  11. nodejs版本管理工具NVM(Node Version Mene)
  12. (转)Java8内存模型—永久代(PermGen)和元空间(Metaspace)
  13. Javaweb里“容器“为何出现,应用在哪,未来发展趋势
  14. matplotlib 将两张数据视图在一起显示
  15. geoserver 图层样式
  16. ionic打包报错Execution failed for task &#39;:processDebugResources&#39;
  17. 关于Java堆、栈和常量池的详解
  18. Redis系列七:redis持久化
  19. 【转】使用import scope解决maven继承(单)问题
  20. JTAG Simplified

热门文章

  1. 面向.Net程序员的Sql版本管理
  2. C# 選擇本機檔案並上傳
  3. Openvswitch原理与代码分析(4):网络包的处理过程
  4. 【Cocos2d-Js基础教学(6)网络层(弱联网)的封装及使用】
  5. [LeetCode] Sparse Matrix Multiplication
  6. mac word 快捷键
  7. 3种归并操作js代码
  8. shell 常用命令
  9. 跟随标准与Webkit源码探究DOM -- 获取元素之getElementsByName
  10. 最近新装系统windows8.1+Mac。。。还没装驱动就遇到一堆问题。。。