系列入口:编程实战:自己编写HTTP服务器(系列1:概述和应答)-CSDN博客
本文是精华,前面所有的铺垫只是为了实现页面数据实时刷新啊!
目录
一、我们想要的实时系统监控界面是什么
理想的实时系统的监控界面是一个监控屏幕,上面各种数字和指示灯闪烁。但是程序只能像流水一样吐出数据,所以前端界面需要做很多事情来解决这个问题。
方案不过是“推”或者“拉”。“推”就是服务端主动发送,客户端等待接收,程序不算简单。“拉”就是客户端定时请求,因为是客户端完全控制,程序比“推”简单。
但是我们希望更简单一些,因为没有前端帮忙,最好一切都在我们的程序里解决。办法不难,需要利用HTTP和HTML的几个小特性。
二、原理
2.1 流水式页面
大部分情况下web页面都是一个完整的页面,有HTTP请求头、有body(就是html页面内容),请求头包含一个“Content-Length”指示内容长度。
但是这不是绝对的,如果没有“Content-Length”,浏览器仍然可以正确处理页面,只不过浏览器会以连接结束作为数据结束的标志(因此不可能使用http1.1的保持连接)。
浏览器处理这种页面的时候(其实也包括所有页面),总是尽快显示内容,不用等到内容结束,能理解多少就显示多少(所以有时候你会看到页面先是显示一些东西,然后布局变化了,可能变化几次,最后才稳定下来)。
所以如果不发送页面结束给浏览器(页面结束是“</body></html>”),不断吐出新内容,浏览器会不停显示的。
这种方式足以显示日志这种流水文件了。前面介绍的执行shell命令处理输出就是这种方式。
2.2 滚动到底部
浏览器不会自动滚动到底部,因此需要嵌入一段JavaScript脚本来让浏览器滚动到底部。
代码在应答对象里面:
void AppendBodyHtmlScroll()//添加HTML滚动
{
m_body += "<script language=\"JavaScript\">window.scrollBy(0,document.body.scrollHeight);</script>\r\n";
}
浏览器遇到这种脚本块就会立即执行。当然,代码中需要立即执行Flash()来把应答发送到浏览器。
2.3 动态更新
流水式页面不是我们最终的目标,我们最终的目标是不刷新页面的情况下更新数据,让浏览器变成一个显示面板。
更新数据首先要知道更新的数据在哪里,一般就用html元素的id来识别,这意味着你需要先学习一下怎么用记事本手撸HTML页面。如果你输出的内容元素的id已经知道了,那么用一行脚本来更新数据就很容易了:
这个代码很简单,但要注意,里面用到了CScriptEncode::Encode(data),功能是把不符合规则的字符替换掉,很显然,data被放在双引号中,双引号和回车换行都是要转义的:
//编码为脚本
class CScriptEncode
{
public:
static string Encode(string const & str)
{
string ret = "";
ret.reserve(str.size() * 2);
for (string::size_type i = 0; i < str.size(); ++i)
{
if ('\"' == str[i])ret += "\\\"";
else if ('\n' == str[i])ret += "\\n";
else if ('\r' == str[i])ret += "\\r";
else ret += str[i];
}
return ret;
}
};
用代码生成代码必须理解这种转义再转义。
现在,我们仍然使用流水式页面,但是输出的只是更新数据的脚本,这样,页面不会重新加载,但数据却活动起来了。
2.4 重新加载页面
浏览器不能支持无限大的页面,因为浏览器把所有数据都放在内存里,用过的也不敢扔掉(谁知道你后面会不会调用呢),所以时间长了整个电脑都会被拖死。
因此要定时重新加载。重新加载当然不能让用户手动点击,只要吐出一行脚本就可以了:
m_respond.AppendBody("<script language=\"JavaScript\">window.location.reload();</script>");
至于何时重新加载就更简单了,比如连续输出一分钟后输出一个这个,然后结束页面。浏览器会重新发起一个连接。隔几分钟重新加载一次用户应该没什么意见。
2.4 更多技巧
完整页面也可以实现定时刷新,不过这就属于纯粹的前端技术了。用C++输出脚本太麻烦?可以写成文件啊,读文件进来啊。不过,这样会增加程序的不确定性,C++程序员应该不喜欢。
三、动态更新的一些提示
动态更新需要知道页面元素的ID,可以按照固定的规则生成ID,程序里应该保持旧数据的一份拷贝,通过比较来针对有差异的数据输出脚本,无差异的就不用输出了。
要确保页面元素确实存在,不然更新到不存在的元素会触发异常,这样用户的感觉就很不好了。
(这里是结束,但不是整个系列的结束)