ZBEmbedeWebServer嵌入式WEB服务器
日期:2010年8月10日
作者:张博
5.1 Scket服务处理接口ISocketServerProcess
1 什么是嵌入式WEB服务器
嵌入式WEB是能够在代码级整合进入软件系统的WEB服务器。
与标准WEB服务器相比,嵌入式WEB服务器通常简化了功能和配置(基本无配置),仅需支持整合项目所需的功能,但又可以针对项目需要增加特别功能。
嵌入式WEB服务器一般不是针对标准WEB开发人员,而是针对C++程序员设计,供C++程序员实现一个定制功能的WEB界面。
在不适合使用标准WEB服务器的场合,比如嵌入式系统,无法支持标准WEB服务器部署,就需要使用嵌入式WEB服务器。
在功能需求很有限但需要快速开发的情况下,在需要特殊后台功能支持的情况下,可以采用嵌入式WEB服务器。
特别,一个C++系统提供自身的服务,经由标准WEB服务器实现可能是繁琐、低效的。标准WEB服务器的方式如CGI、ASP、ISAPI或者PHP,这些方式都可以调用C++功能,但是,实际上很少有人开发人员从事用WEB方式调用C++功能的工作。而且由于WEB开发人员和C++开发人员技能的差异,协作开发的沟通成本是巨大的,远不如仅由C++人员开发方便。
比如监控远程主机的程序,基本的方式是使用telnet远程登录,但telnet只能监控一个日志文件,作为大型系统的界面是过于原始的。采用标准WEB服务器,再开发大量专门的监控程序供网页使用又需要很大的工作量。采用嵌入式WEB服务器就成为一个很好的选择。采用嵌入式WEB服务器,C++程序员所能够输出的信息不再有任何限制,程序里的任何信息都可以直接输出到网页。
本嵌入式WEB服务器就是专门为C++程序员专门定制的,为C++程序员提供了一组方便的组件,无需WEB知识就可以把程序结果输出为WEB页面,并可以用C++方式产生WEB表单和处理表单。
2 WEB Service支持
本服务器提供了输出XML的方法,可以支持时下流行的客户端页面+WEB Service的开发模式。
WEB Service通过HTTP提供XML格式的数据,客户端不需要刷新整个页面,仅通过WEB Service获取部分数据,可以提供较好的用户体验。这种方式也把数据显示和数据提供完全分开,方便网页开发人员和后台程序员协作。
3 功能图
接受连接 用户认证,至少支持basic |
根据请求信息分别处理,按顺序匹配 |
/bin/*.Asp:静态编译,内置标准页面,有统一的用户定义的页眉、页脚,内容由程序自行输出 |
/bin/*.Aspx:动态链接,标准表格输出,程序只需把数据填入CHtmlTable对象 |
/bin/*.Asmx:动态链接,WEB Service,标准XML输出,程序只需把数据填入CHtmlTable对象 |
/shell.asp:内置功能,运行一个命令行并把输出生成页面显示,关键信息自动加亮上色 |
/ViewFile.asp:内置功能,显示文件,分页显示,并可以自动对关键信息加亮上色,用于显示日志 |
/DownFile.asp:内置功能,下载文件 |
其余:静态文件 |
/bin/*:动态链接,完全由用户生成页面 |
4 演示
4.1 页眉和功能目录的上半部分
4.2 功能目录的下半部分和页脚
4.3 用户定义的功能
4.4 运行后台命令
4.5 显示文件(查阅日志)
4.6 动态链接库
通过动态链接库“dbquery”执行查询“select * from sysconfig”
Aspx方式,直接输出为简单表格:
Asmx方式,输出XML:
4.7 WEB Service
这个页面背后使用了多个动态链接库提供的XML数据,数据更新时页面无需刷新,定时调用asmx页面获取XML数据。
标准用户功能页面具有统一的界面格式,虽然简化了编程但缺乏灵活性,不能实现丰富多彩的页面。
与标准用户功能页面不同,这个页面只有数据来自服务器,所有界面特性都由客户端页面控制,界面的复杂度取决于界面开发人员的能力,完全不再受制于服务器能力。
5 对象和功能
5.1 Scket服务处理接口ISocketServerProcess
处理一个Socket请求,供通用Socket服务器调用
5.2 通用Socket服务CSocketServer
多进程通用Socket TCP服务器,通过ISocketServerProcess处理请求。
5.3 WEB服务CHttpProcess
实现ISocketServerProcess接口,提供功能图中列举的系统关键功能。
支持HTTP1.1的持久连接。
5.4 HTTP请求CHttpRequest
经过分析的HTTP请求,各个部分已经分析完成,可以通过成员函数直接调用。HTTP请求分解后包括请求的文件名称、查询参数、请求附加信息、提交的表单。
用户定义的功能都通过这个对象读取请求信息。
5.5 HTTP应答CHttpRespond
HTTP应答对象。用户定义的功能可以通过这个对象修改应答头、添加应答输出。本系统提供了方便的输出HTML内容的方法。
5.6 HTTP命令CWebCommand
一个WEB表单并和处理程序关联,用于实现/bin/*.asp页面,服务器根据表单定义自动生成表单,表单由关联的程序处理。
5.7 动态链接库调用接口IAsmxDllMain
动态链接的功能的接口,生成完全用户定义的页面,对此接口存在包装类IPluginAsmx来生成包括/bin/*.aspx和/bin/*.asmx的标准表格或XML。
5.8 WEB Service接口IPluginAsmx
实现WEB Service的接口,根据后缀名aspx或asmx的不同生成标准表格或XML(即WEB Service)。
5.9 HTML文档支持
5.9.1 HTML、URL等编码解码
CHtmlEncode将普通文本编码为HTML格式。
URLEncode将普通文本编码为URL格式。
XMLEncode将普通文本编码为XML格式。
5.9.2 表单参数CWebCommandParam
HTTP命令CWebCommand的参数,对应表单的条目。
5.9.3 表格CHtmlTable
该表格对象的功能远远超越了WEB表格,除了可以生成WEB表格之外,还可以生成XML文件。
该表格对象可以对每行数据生成一个表单进行添加、修改、删除。
该对象还有一个特殊功能,可以根据数据变化生成更新脚本。采用服务端推技术,源源不断地把更新脚本推送到客户端,客户端就可以在不刷新页面的情况下展示出动态的数据变化。这是专门为实时监控远程程序定制的。
6 使用基本服务
6.1 整合入系统
本系统提供的源代码全部为C++头文件,加入系统即可,无需修改编译配置。本系统兼容UNIX和Windows。
包含头文件:zbhttpserver.h
6.2 生成基本服务
//定义Socket服务对象和HTTP处理对象,realm指明领域,领域用于HTTP的basic用户认证,类似站点的名字
CSocketServer svr;
CHttpProcess process(realm);
//设置根目录,HTTP提供一个虚拟的目录树,这里指明“/”对应的目录
process.SetRoot(root.c_str());
bool isShutDown;
long ret;
//Start启动端口
if(svr.Start(portnum))
{
//Accept接受一个请求并用process处理,第二个参数false规定使用多进程,这样就可以同时处理多个请求
while(svr.Accept(&process,false,&isShutDown,&ret))
{
}
}
6.3 设置服务器特征
下面这些操作都是可选的,在Start调用之前设置好即可
//定义一个命令组,包含所有的用户命令
vector<CWebCommand > cmds;
。。。。。。添加命令到cmds。。。。。。
process.SetCommands(cmds);//设置用户命令
svr.SetPfForFork(ForFork);//用于UNIX,设置复制进程前后的操作,参数为函数地址
process.SetCheckUser(CUserManager::CheckUser);//设置用户认证,参数为函数地址
process.SetCheckAdmin(CUserManager::CheckAdminPassword);//设置admin用户认证,这是一个特殊的用户,密码可以用与普通用户不同的方式管理
process.SetCheckSys(CheckSys);//设置用户命令处理之前检查系统的函数
process.SetPageHead(GetPageHead);//设置生成内置页面和用户命令页眉的函数,使用函数而不是简单字符串可以支持动态内容
process.SetSiteName(buf);//设置站点名称,会显示在内置页面和用户命令的标题栏
process.SetPageFoot(“。。。”);//设置内置页面和用户命令的页脚,一般就是版权声明,所以无需动态
6.4 编写用户命令
CWebCommand定义了一个用户命令,对应一个表单和一个处理程序,处理程序接口为:
typedef bool T_COMMOND_FUNCTION(void* p,CHttpRequest const * pRequest,CZBSocket & s,CHttpRespond * pRespond);
用户命令可以pRespond输出HTML,用户输出的内容只是页面body的一部分,页面的框架部分由服务器输出。日志输出经过了设置,会直接输出到页面。
CWebCommand存在许多属性设置表单的特性。服务器根据设置生成一个表单,表单提交时传输给用户命令。用户命令执行输出页面内容。
6.5 编写动态链接库
动态链接库入口点:
class IAsmxDllMain
{
public:
virtual bool asmx_exec(CHttpRequest const * ,CZBSocket & ,CHttpRespond * )=0;
};
//asmx页面的dll入口类型
typedef IAsmxDllMain * T_ASMX_DLL_MAIN(void * pActiveAppEnv);
使用此接口编写动态链接库生成用户自定义页面,接口中不会输出任何统一信息,所有信息,包括HTTP应答头,全部由用户输出。
6.6 编写WEB Service
WEB Service用动态链接库实现。提供了声明动态链接库入口点的宏和WEB Service接口。
//声明DLL入口点
#define DECLARE_asmxDllMain(classname) /
extern "C" IAsmxDllMain * asmxDllMain(void * pActiveAppEnv)/
{/
g_pActiveAppEnv=(CActiveAppEnv*)pActiveAppEnv;/
/*InitApp();*//
return new classname;/
}
//WEB Service接口
class IPluginAsmx : public IAsmxDllMain
{
private:
//重载此函数以支持实际功能
virtual void _asmx_exec(CHtmlDoc::CHtmlTable2 & table,CHttpRequest const * pRequest)=0;
public:
//不可重载此函数
virtual bool asmx_exec(CHttpRequest const * pRequest,CZBSocket & s,CHttpRespond * pRespond)
};
编写一个实际功能类继承自IPluginAsmx,以类名为参数实现DECLARE_asmxDllMain(classname)。
_asmx_exec的参数仅包括HTTP请求对象pRequest和输出对象table,IPluginAsmx根据资源后缀名的不同决定结果输出格式,“aspx”类型输出为表格,“asmx”类型输出为XML。
7 使用特殊功能
7.1 无刷新实时监控
下面的代码演示了一个无刷新实时监控页面,每秒钟输出一次更新脚本,每分钟整体更新一次页面(考虑到一些浏览器的特殊问题)。代码为asp内置功能接口,也可以同样用于动态链接库接口。使用asp内置功能接口时命令对象必须设置SetNotStdPage()避免服务器输出标准框架。
具体方法为:
1,初始化HTTP应答,样例代码所作的设置是最基本的。
2,输出HTML头部。
3,输出页面内容。
4,循环输出数据更新脚本。CHtmlTable对象可以通过比较两个对象的差别来输出更新脚本,因此需要保留上次生成的对象。样例代码中GetHtmlState函数通过第一个参数确定是输出表格还是更新脚本,函数内部用静态变量保存上一次的表格。
5,输出脚本命令浏览器刷新整个页面。
6,输出HTML尾部。
7,如果不能使用持久连接,关闭Socket。持久连接适用于标准静态页面,对于用户定义页面可能不适合。
页面效果为一个静态页面,上面的数据每秒钟更新一次。每分钟整个页面刷新一次。
static bool HBCenter(void* p,CHttpRequest const * pRequest,CZBSocket & s,CHttpRespond * pRespond)
{
pRespond->Init();
pRespond->AddHeaderNoCache();
pRespond->AddHeader("Content-Type","Text/html");
pRespond->AppendBody("<html><body>/r/n");
//第一次输出表格
GetHtmlState(false,processinfo,pRequest,s,pRespond);
long count;
for(count=0;(count)<60;++count)
{
//只输出更新脚本
GetHtmlState(true,processinfo,pRequest,s,pRespond);
if(!pRespond->Flush(s))
{
s.Close();
break;
}
sleep(1);
}
pRespond->AppendBody("<script language=/"JavaScript/">window.location.reload();</script>");
pRespond->AppendBody("</body></html>");
pRespond->Flush(s);
s.Close();
return true;
}