我知道这个问题如果要回答,就得写得很长,所以也拖好久用 C++ 支撑写Web页面,有两种截然不同的思路由于C++ 是传统桌面图形界面(GUI)的主力,所以第一种思路就是把重心放在如何用“桌面GUI”的方式,模拟甚至“屏敝”网页上的UI 。
先说我对此的看法吧,我认为这种思路看起来很“自然”,但其实是条不归路没错,一旦你要用C++这样的静态语言,去模拟一套由“HTML + CSS + JS”这样一套完全是描述性加动态语言组成的界面世界,真的就会吃力不讨好:既丢失动态的好处,又得不到。
静态语言的好处以Wt为例,它的名字或许就有模拟桌面GUI系统中,非常强大“Qt”的意味来看它自己的一个教学例子吧这个例子是为了实现页面上的一棵树效果在这里:HTML页面上的“Tree”组件实际这就样一棵树:。
这样一个HTML组件,如果你的前端技术好并且时间闲,你可自己用 HTML+CSS+JS 硬撸如果你这觉得自己写太麻烦了,并且还不好看,没有美感那更正常的方法是,是找别人写的现成的组件——这里重点的是:别人写的组件,也是使用前端技术写的 ,有原生的HTML+CSS+JS,有使用JQuery的,有结合前端流行框架的(Vue、React等)……但都仍然是通用的前端技术,和你后端的语言无关;后端爱用 Java、用PHP、用Node.JS、用Python,或者用静态语言 Go、Rust、C++、甚至 C 。
统统可以没有哪一个前端UI库说:对不起,我这套界面库“认”后端语言,只能用 PHP和Go,不支持 Python和C++……一套前端UI库,根本不应该要求后端使用语言这既是符合“松耦合”的好设计,也是有利于研发分工的自然(不加双引号)思维。
一套前端UI库,非要绑定后端语言,基本只在一需求情景下存在:企图完全让只用后端程序员来实现前后端整套系统(注意:这并不是所谓的“全栈”工程师)上面那个“树”组件,尽管它最终呈现在浏览器页面里时,可能是用HTML里的
我们先按“自顶向下”思路,也就是将组件功能由大拆小的分析方法:嗯,首先,这是一棵“树”,所以有“父节点”和“子节点”……然后,每个节点都有折叠和展开两种状态……哦,每个节点又包含两个组成:图标(文件夹)和文字;其中的图标也要满足第2点中提到的两种可切换的状态“折叠和展开”。
……好吧具体实现时,我们换成“自底向上”,也就是先写小组件,再由小组件组成大组件的思路……第一个小组件,就是那个“双态”图标吧所以,这是官方例程讲解出,给出的第一段代码://图标“对”,也就是我们说“双态图标”,因为它其实是由两个图标组成 。
classIconPair:publicWCompositeWidget{public:IconPair(conststd::stringicon1URI,conststd::stringicon2URI
,boolclickIsSwitch=true);voidsetState(intnum);intstate()const;WImage*icon1()const{returnicon1_;}WImage
*icon2()const{returnicon2_;}voidshowIcon1();voidshowIcon2();private:WContainerWidget*impl_;WImage*icon1_
;WImage*icon2_;public:EventSignal*icon1Clicked;EventSignal*icon2Clicked;private
:intpreviousState_;voidundoShowIcon1();voidundoShowIcon2();};然后,该到树的“节点”了:classTreeNode:publicWCompositeWidget
{public:TreeNode(conststd::stringlabelText,TextFormatlabelFormat,std::unique_ptrlabelIcon);
voidaddChildNode(std::unique_ptrnode);voidremoveChildNode(TreeNode*node,intindex);conststd::
vector&childNodes()const{returnchildNodes_;}voidcollapse();voidexpand();private:std::vector
childNodes_;TreeNode*parentNode_;WTable*layout_;IconPair*expandIcon_;WImage*noExpandIcon_;
IconPair*labelIcon_;std::unique_ptrlabelText_;WText*childCountLabel_;WContainerWidget*expandedContent_
;voidadjustExpandIcon();boolisLastChildNode()const;voidchildNodesChanged();boolwasCollapsed_;voidundoCollapse
();voidundoExpand();enumImageIndex{Middle=0,Last=1};staticstd::stringimageLine_[];staticstd::stringimagePlus_
[];staticstd::stringimageMin_[];};// 有了节点,就能构建树了……但是够了这就是完全在构建一套“自己的世界”这是很恐怖的比如事实上那棵树,Wt最终会用HTML中的“表格/TABLE”来模拟——这是很古老的技术。
稍微现代点的前端组件库,都会用效果更好,更方便定制,包括很可能更容易响应小屏(手机)的div实现……但在Wt的设想里,这些你不应该去关心,所以更不应该有多种“选择”的思想嗯界面不好看(因为没办法控制细节),最终实现方法落后于时代,并且没有丰富的选择项……但是,你只需要懂一门语言,就可以实现前后端啊,少了很多学习成本,这难道不是优点吗?不。
你懂C++,现在Wt这么一套真的很不小的库在你眼前,你也要去学习它啊并且!由于你交给用户的最终产品技术支撑其实是 前端那一套;所以很多时候,你哪怕只是为了抓紧知道“怎么用”,你也不得不去研究前端技术我是以C++为主的老程序员了,我也学习并用开发前端技术,我可以很负责地说:读Wt的文档,例程、再试用、遇上问题再研究明白,这个时长,我认为会比我学习、上手HTML\CSS\JS,然后再中上 Vue这样一套(渐进式)的前端框架,都要长!先看看Wt的类库文档目录:。
Wt Namespace Referencewww.webtoolkit.eu/wt/doc/reference/html/namespaceWt.html所有想用C++语言写UI,然后底层强行再转换至机理完全不一样的另一套UI(HTML+JS+CSS)的库,都会很复杂难懂。
并且由于小众,所以遇上坑会非常难找到答案而像Wt这样虽然是开源,但明显很“公司化”的提供商,我努力找到它的官方上手教程,发现教程很弱——我猜它是希望你用,然后有问题找他帮助的……(想起我曾经在IBM的AIX上贪便宜用不要钱的GCC,结果遇上问题联系对方后,才知道。
咨询服务更贵)当然啦Wt也知道这些缺点……事实它也有另一种补充:假设你也擅长前端,或者为了更好的控制UI的展现细节,那么它也是有提供wtTempate这样的类,容许你在某些地方以前端为主,然后它来配合……但这更多是就是在补丁上打补丁。
所以,Wt的特点:全、大但是走的是“大包大揽”和“大集成”的封闭路线如果“在完全不懂Web前端UI技术下实现最终是Web作为前端的软件系统”这一点对你非常重要那可以试试说完Wt,再说 CppCMS,就轻松了一些了。
CppCMS说:我也可包揽前端UI的实现;但我退让一点:你得懂点前端技术哦比如,这是它的官方教程中,开始涉及前端时就有的例子: <
% skin my_skin %>
World!
你看到什么?一堆 <>,还有 html、 body等字眼——这些是前端的技术中HTML但你还看到,这里面居然有 “ #include “content.h” ”这样的C++风格的代码——事实上,这是十多年前的流行的老技术了。
什么PHP、JSP、ASP都是在 HTML的页面代码中,夹点后台语言所以你可以将上面这个,称为“CSP” 想要对页面的做更多美化、细节处理有这个文件就很方便,因为你大可利用上你的前端技术,比如CSS或JS,直接或间接嵌入上面那个文件。
比如:希望 那句问候可以变成红色:那就:
World!
接下来是CppCMS的另一个同样是很典型的关键技术点:这个既有后台语言又有前端语言的模板文件,最终是如何变成纯的前端网页的?CppCMS的方法是:我要先把它“编译” C++源代码,然后再编译成可执行文件
,然后再于运行时期,由可执行文件向浏览器“吐出”纯的HTML页面……意思是,上面的文件,其实要先变成类似以下的C++代码:#include”content.h”…………cout<<“\n”cout
<<“\n”……cout<<“
“<<“Hello World!
“;cout<<“\n\n”在编译阶段将它们变成C++代码,自然的好处就是这个“变化”是在程序运行前就发生的,因此到时运行时速度很快。但得到性能保障时,得到的缺点也很可气:你想修改一个某个展现效果你就是再次编译程序(事实上还得先“编译”出源代码……)所以,这套技术虽然也曾经很流行,并且显然并不是只有C++这么干的;但,也相信我,这种技术也是“俱往矣”。
当然,CppCMS也知道这个缺点所以它还有一个选项:那就是它干脆完全不管界面元素的事了,它可以做作为一个纯粹的数据“接口”,用的是“JSON-RPC”但是,这又撇得太清了因为一个“JSON-RPC”的后端,事实上都可以不认为是一种Web框架,你拿它为手机APP写后端数据服务也是妥妥的,并且性能很好。
如果前端仍然是浏览器,那要么就是使用前端模板渲染引擎或前端框架(Vue很合适),要么就得在浏览器和JSON-RPC之间,再插一层……如果你或你队伍中有人懂前端,并且会某种前端模板渲染引擎(基本就是基于JS的,但可以是jQuery或的Vue),那么可以把CppCMS就当成是一个基于C++的高效的后台数据服务框架使用。
如果你觉得前端渲染引擎会让页面初始展现时有点“抖”,那你可以试试CppCMS的全功能模式:即先写模板,然后“编译”为C++源代码,再然编译成一个C++程序……当然,你要能容忍页面哪怕是一个颜色改动,这两步动作都得重来一次(这当中网站当然得重启一次)。
如果不能容忍呢?改为用“TreeFrog”吗?不,这个TreeFrog 我没有实际使用,只是看它的文档:High-speed C++ MVC Framework for Web Application
www.treefrogframework.org/ch/user-guide/view/otama-template-system.html
中的这张图:
就能知道,它和CppCMS的全功能模式是一样的不过它的模板系统似乎不只一套,或许会有一套别的模板机制,不需要先“编译”成C++代码……TreeFrog的另一个特点,就是它不是“像”Qt,而是它就是用Qt实现的。
我没用它,就是这个原因因为写后台网络,我更喜欢原生就是异步的C++网络库肯定猜到了我说的是:asio前面的CppCMS用的是asio再次强调一下,由于没有实际学习使用 TreeFrog,所以我不能确定,它的模板机制是不是一定只能静态编译。
不过,从树蛙前进到下一个库“Drogon ”,可以明确是既支持静态编译(模板文件变成C++代码文件),也支持在程序运行时,动态编译加上文件变动检测机制,那么不重启网站后端程序,修改页面非数据的展现就没问题了。
这是Drogon的页面模板,它还真还在叫“CSP”:<%c++ auto para=@@.get
,std::string>>(“parameters”); %> [[ title ]]
0){%>
Parameters
namevalue> {%iter.first%}<%c++ $$<
no parameter
和“CppCMS”原理是一样的:HTML混着C++代码。只是,它比“CppCMS”优秀的地方,在于它可以选择静态编译,也可以选择在运行期动态编译成C++代码写这样的模板,你当然得懂前端技术——或者你这位C++大后端也可以不需要懂前端HTML,但就得要求前端工程师,他们懂点C++了——在现在,这个要求挺令人发指的……好了!关于要不要求懂前端这个话题,我们准备结束了。
我们要进入下一个主题话题:什么是“现代的” Web 框架?事实上,当前进到“现代风格”的Web Frame,除了自身及周边积累的配套产资源有多有少之外,就框架自身而言,真的没有多少语言之分了,如此,这个问题甚至可以加入Go或Node.JS或Rust这样和C++相比要年轻得多的语言的各个Web框架……真的没有什么本质上的“开发体验”之区分。
后面我会以以 C++、Go、 Node.JS 、Rust的 现代 Web 框架来对比现在继续讲DrogonDrogon的缺点(在我看来),就是没有摆脱“面向对象”的思路——并不是“面向对象”不好,而是在Web这样典型的后端业务层逻辑本就应当“无状态”(对应HTTP请求)的场景,还在使用擅长状态维持的“类与对象”的设计,再加上非常容易叠床架屋的派生机制……比如(例子来自Drogon的github站,略有简化):
classA:publicDrObjectBase{public:voidhandle(constHttpRequestPtr&req,std::function
&)>&&callback,intp1,conststd::string&p2)const{HttpViewDatadata;data.insert(“title”,std::string(“ApiTest::get”
));std::unordered_mappara;para[“int p1”]=std::to_string(p1);para[“string p2”
]=p2;data.insert(“parameters”,para);autores=HttpResponse::newHttpViewResponse(“ListParaView”,data);callback
(res);}};这个 handle() 方法,完全看不出为什么需要是成员函数?它就应该是一个自由函数——你只需看类A,可有任何状态???不考虑基类,它甚至就是一个成员数据都不需要呢而说到基类,也看不出基类在此间的作用。
有些为类而类,为派生而派生再者,它的response对象,在handle内创建,然后handle却要求传入一个回调(callback),这样做,我认为是有些“叠床架屋”的过度设计了不信,我们还是先跳出C++,来看别的年轻语言各种框架的设计吧(下面的框架,基本都是在运行期进行模板与数据的合并渲染)。
我们从Node.JS开始:Node.JS / Expressapp.get(/hello,(req,res)=>res.send(Hello World!))app.run();JavaScript 在这里有点占语法的便宜了,我们给个公平版的:
app.get(/hello,function(req,res){res.send(Hello World);});app.run();这样清楚多了: app 在 有人访问 网站 的 /hello 网址时,将调用一个函数,这个函数使用 res 对象返回“Hello World”。
再看 Go :Golang / Iris:app.Handle(“GET”,”/”,func(ctxiris.Context){ctx.HTML(“Hello World”)})ctx 是“上下文”,实际包含了 请求 (req)和响应(res)。
同样功能也可以写得更简化一些,而一简化,就是和Node.JS/Express的例子更接近了:app.Get(“/hello”,func(ctxiris.Context){ctx.WriteString(
“Hello World”)})…Rust这样的,比C++都烧脑,严格的语言,人家也可以有Web框架……但其实语言差异又何妨呢?只要一投身到相同的Web业务,大家都开始变得像了:Rust / Actix
asyncfngreet(req: HttpRequest)-> implResponder{letname=req.match_info().get(“name”).unwrap_or(“World”
);format!(“Hello {}!”,&name)}…当然,没有人敢否认,Rust是一门符号用得够够的语言……该说回C++了,题主问到的最后一个:“Crow”:C++ / CROWCROW_ROUTE
(app,”/hello”)([](){returncrow::response(“Hello World”);});C++ / Cinatra#include”cinatra.hpp”usingnamespace
cinatra;intmain(){http_serverserver(8);server.listen(“0.0.0.0″,”8080”);server.set_http_handler
>(“/”,[](request&req,response&res){res.set_status_and_content(status_type::ok,”hello world”);});server
.run();return0;}注意到了吗? req进来,res出去……这就是典型的“面向过程”最后是我的:C++ / da4qi4#include”daqi/da4qi4.hpp”usingnamespace。
da4qi4;intmain(){autosvc=Server::Supply(4098);svc->AddHandler(_GET_,”/”,[](Contextctx){ctx->Res().ReplyOk
(“Hello World!”);ctx->Pass();});svc->Run();}我有次出于无聊……一天把上面提到的所有语言,都找出3个框架,同一语言内部先比较,再跨语言比较……当时 结论就是:原来我生活在2019年。
你可能想看看一个C++ Web 框架 使用模板文件 效果:
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容