同步操作将从 zhuangyan-stone/da4qi4_public 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
官网 第2学堂 www.d2school.com 后台使用 da4qi4作为Web Server开发。(nginx + da4qi4 + redis + mysql)。 给一个在手机上运行的网站效果(丑,但这只和我的美感太差有关,和后台使用什么Web框架一分钱关系都没有):
使用成熟的,广泛应用(最好有大公司参与)的开源项目作为框架基础组成部件。
da4qi4 Web 框架优先使用成熟的、C/C++开源项目的搭建。其中:
注:不绑定数据库访问方式。用户可使用 Oracle 官方 C++ Connector,或MySQL++或见:三、运行时外部配套系统。
使用C++开发,基于异步框架,目的就是为了有一个较好的原生性能起点,开发者不要过于费心性能。框架易用性设计高于性能设计。 暂时仅与 Tomcat 做了一个比较。由于Tomcat似乎是“Per Connection Per Thread”,所以这个对比非常“胜之不武”;但考虑到Tomcat曾广泛应用于实际系统,所以和它的对比数据有利于表明da4qi4在性能上的可用性。
基准测试环境:
Tomcat 运行配置
JVM 1.8G 内存
最大线程数:10000
最大连接数:20000
最大等待队列长度 200
对 Tomcat不算熟,因此以上配置基本照着网上的相关测试指南设置,有不合理之处,望指正。
- | 并发数 | 平均响应(ms) | 响应时间中位数(ms) | 99% 用户响应时间(ms) | 最小响应(ms) | 最大响应(ms) | 错误率 | 吞吐量(s) | 每秒接收字节(KB) |
---|---|---|---|---|---|---|---|---|---|
tomcat | 1000 | 350 | 337 | 872 | 1 | 879 | 0 | 886.7 | 273 |
da4qi4 | 1000 | 1 | 1 | 20 | 0 | 24 | 0 | 1233 | 286.6 |
有性能测试经验的人,应该能知道表中“da4qi4”的平均响应时长等数据,出现1毫秒的情况,这基本是因当前测试数据远远喂饱受测对象的表现。
另,官网 www.d2school.com 一度以 1M带度、1核CPU、1G 内存的一台服务器作为运行环境(即:同时还运行MySQL、redis服务);后因线上编译太慢,做了有限的升级。
后续会给出与其他Web Server的更多对比。但总体上,da4qi4 的当前阶段开发,基本不会以极端性能提升作为目标。
这点没什么好说的,一个C++程序员走上工作岗位后必须拥有的基本素养:用你的能力把系统写简单,而不是把系统写复杂以证明你的能力。把系统做简单够用。克制把系统做复杂的冲动。
我们需要一个C++文件,假设名为“main.cpp”,内容如下:
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
auto svc = Server::Supply(4098);
svc->Run();
}
不到10行代码,我们创建了一个空转的,似乎不干活的Web Server。
编译、运行,然后在浏览器地址栏输入:http://127.0.0.1:4098 ,而后回车,浏览器将显示一个页面,上面写着:
Not Found
小提示:代码中的“Supply(4098)”调用,如果不提供4098这个入参,那么Web Server将在HTTP默认的80端口监听。我们使用4098是考虑到在许多程序员的开发电脑上,80端口可能已经被别的应用占用了。
虽然说它“不干活”,但其实这个Web Server在合符逻辑地正常运行中:你没有为它指定任何操作绑定,所以对它的任何访问,都返回“Not Found”(没错,就是404)。
不管访问什么都回一句“Not Found”这令人沮丧。接下来实现这么一个功能:当访问网站的根路径时,它能答应一声:“Hello World!”。
这需要我们大代码中指示框架遇上访问网站根路径时,做出必要的响应。响应可以是函数指针、std::function或C++11引入的lambda,我们使用最后者:
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
auto svc = Server::Supply(4098);
svc->AddHandler(_GET_, "/", [](Context ctx)
{
ctx->Res().ReplyOk("Hello World!");
ctx->Pass();
});
svc->Run();
}
例中使用到的Server类的AddHandler()方法,并提供三个入参:
指定的HTTP访问方法: _GET_;
指定的访问URL: /,即根路径 ;
匿名的lambda表达式。
三个入参以及方法名合起来表达一个意思:如果用户以GET方法访问网站的根路径,框架就调用lambda 表达式以做出响应。
编译、运行。现在用浏览器访问 http://127.0.0.1:4098 ,将看到:
Hello World!
以上代码返回给浏览器纯文本内容,接下来,应该来返回HTML格式的内容。出于演示目的,我们干了一件有“恶臭”的事:直接在代码中写HTML字符串。放心,后面很快会演示正常的做法:使用静态文件,或者基于网页模板文件来定制网页的页面内容;但现在,让我们来修改第11行代码调用ReplyOK()函数的入参,原来是“Hello World!”,现在将它改成一串HTML:
……
ctx->Res().ReplyOk("<html><body><h1>Hello World!</h1></body></html>");
……
接下来,我们希望请求和响应的内容都能够有点变化,并且二者的变化存在一定的匹配关系。具体是:在请求的URL中,加一个参数,假设是“name=Tom”,则我们希望后台能返回“Hello Tom!”。
这就需要用到“Request/请求”和“Response/响应”:
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
auto svc = Server::Supply(4098);
svc->AddHandler(_GET_, "/", [](Context ctx)
{
std::string name = ctx->Req("name");
std::string html = "<html><body><h1>Hello " + name + "!</h1></body></html>";
ctx->Res().ReplyOk(html);
ctx->Pass();
});
svc->Run();
}
编译、运行。通过浏览器访问 “http://127.0.0.1:4098/?name=Tom” ,将得到带有HTML格式控制的 “Hello Tom!”。
小提示:试试看将“Tom”改为汉字,比如“张三”,通常你的浏览器会在“Hello ”后面显示成一团乱码;这不是大器框架的问题,这是我们手工写的那段html内容不够规范。
Server代表一个Web 服务端,但同一个Web Server系统很可能可分成多个不同的人群。
举例:比如写一个在线商城,第一类用户,也是主要的用户,当然就是来商城在线购物的买家,第二类用户则是卖家和商城的管理员。这种区别,也可以称作是:一个服务端,多个应用。在大器框架中,应用以Application表达。
就当前而言,还不到演示一个Server上挂接多个Application的复杂案例,那我们为什么要开始介绍Application呢?Application才是负责应后台行为的主要实现者。在前面的例子中,虽然没有在代码中虽然只看到Server,但背后是由Server帮我们创建一个默认的 Application 对象,然后依靠该默认对象以实现演示中的相关功能。
现在我们要做的是:显式创建一个Application对象,并代替Server对象来实前面最后一个例子的功能。
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
auto svc = Server::Supply(4098);
auto app = Application::Customize("web", "/", "./log");
app->AddHandler(_GET_, "/", [](Context ctx)
{
std::string name = ctx->Req("name");
std::string html
= "<html><body><h1>Hello " + name + "!</h1></body></html>";
ctx->Res().ReplyOk(html);
ctx->Pass();
});
svc->Mount(app);
svc->Run();
}
发生的变化:使用Aplication类的静态成员函数,定制(Customize)了一个应用,例中命名为web_app;然后改用它来添加前端以GET方法访问网站根URL路径时的处理方法。最后在svc运行之前,需要先将该应用挂接(Mount)上去。
这段代码和前面没有显式引入Application的代码,功能一致,输出效果也一致。但为什么我们一定要引入Application呢?除了前述的,为将来一个Server对应多个Application做准备之外,从设计及运维上讲,还有一个目的:让Server和Application各背责任。 Application负责较为高层的逻辑,重点是具体的某类业务,而Server则负责服务器较基础的逻辑,重点是网络方面的功能 。下一小节将要讲到日志,正好是二者分工的一个典型体现。
一个Web Server在运行时,当然容易遇到或产生各种问题。这时候后台能够输出、存储运行时的各种日志是大有必要的功能。
结合前面所说的Server与Application的分工。日志在归集上就被分成两大部分:服务日志和应用日志。
服务层日志:记录底层网络、相关的周边运行支撑环境(缓存/Redis、数据库/MySQL)等基础设施的运行状态。
应用层日志:记录具体应用的运行日志。
其中,相对底层的Server日志全局唯一,由框架自动创建;而应用层日志自然是每个应用对应一套日志。程序可以为服务层和应用层日志创建不同的日志策略。事实上,如果有多个应用,那自然可以为每个应用定制不同的日志策略。
在前面例子中,服务层日志对象已经存在,只是我们没有主动配置它,也没有主动使用它记录日志。而唯一的应用web_app则采用默认的日记策略:即没有日志。没错,应用允许不记录日志。
为了使用服务层日志,我们需要在程序启动后,服务还未创建时,就初始化服务层的日志对象,这样才有机会记录服务创建过程中的日志(比如最常见的,服务端口被占用的问题)。假设初始化日志这一步都失败,记录失败信息的人,肯定不能是日志对象,只能是我们熟悉的std::cerr或std::cout对象。
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
if (!log::InitServerLogger("/home/zhuangyan/Projects/CPP/daqi_demo/www/logs", log::Level::debug))
{
std::cerr << "Create server logger fail." << std::endl;
return -1;
}
auto svc = Server::Supply(4098);
log::Server()->info("准备web应用中……");
auto web_app = Application::Customize("web", "/", "./log");
web_app->AddHandler(_GET_, "/", [](Context ctx)
{
std::string name = ctx->Req("name");
std::string html = "<html><body><h1>Hello " + name + "!</h1></body></html>";
ctx->Res().ReplyOk(html);
ctx->Pass();
});
svc->Mount(web_app);
svc->Run();
}
小提示:为方便演示,上面代码暂时使用绝对路径以指定服务层日志文件的存储位置,实际项目通常以相对路径,或读取外部配置的方式以方便部署。
一旦“InitServerLogger()”调用成功,并且设置低于INFO的日志输出级别(例中为DEBUG级别,见该函数的第2个入参:log::Level::debug),框架中有关服务层的许多日志,就会打印到屏幕(控制台)上及相应的日志文件里。
下面是运行日志截图示例。
当然,当程序运行在服务器上,它会被配置成在后台运行,没有界面,日志被重定向到文件。
是时候解决在代码中直接写HTML的问题了。
用户最终看到的网页的内容,有一些在系统设计阶段就很清楚,有一些则必须等用户访问时才知道。比如前面的例子中,在设计时就清楚的有:页面字体格式,以及“Hello, _ _ _ _ !”;而需要在运行时用户访问后才能知道的,就是当中的下划线处所要填写的内容。
下面是适用于本例的,一个相当简单的的HTMl网页模板:
<!DOCTYPE html>
<html lang="zh">
<head>
<title>首页</title>
<meta content="text/html; charset=UTF-8">
</head>
<body>
<h1>你好,{=name=}!</h1>
</body>
</html>
“你好,”后面的特定格式{=name=},将会被程序的模板解析引擎识别,并填写上运行时的提供的name的值。使用模板后,现在用于产生的网页内容的完整C++代码如下:
#include "daqi/da4qi4.hpp"
using namespace da4qi4;
int main()
{
//网站根目录 (同样,我们暂使用绝对路径)
std::string www_root = "/home/zhuangyan/Projects/CPP/daqi_demo/www/";
//初始化服务层日志
if (!log::InitServerLogger(www_root + "logs", log::Level::debug))
{
std::cerr << "Create server logger fail." << std::endl;
return -1;
}
auto svc = Server::Supply(4098);
log::Server()->info("准备web应用中……"); //纯粹是为了演示日志……
auto web_app = Application::Customize("web", "/",
www_root + "logs/",
www_root + "static/",
www_root + "view/",
www_root + "upload/");
//初始化web应用日志
if (!web_app->Init(Application::ActualLogger::yes, log::Level::debug))
{
log::Server()->critical("Init application {} fail.", web_app->GetName());
return -2;
}
//添加请求处理
web_app->AddHandler(_GET_, "/", [](Context ctx)
{
std::string name = ctx->Req().GetUrlParameter("name");
ctx->ModelData()["name"] = name;
ctx->Render().Pass();
});
svc->Mount(web_app);
svc->Run();
}
由于我们所写的模板文件正确地指定了相关编码,所以现在如果访问 http://127.0.0.1:4098?name=大器da4qi4 。将得到带有HTML格式的:
你好,大器da4qi4!
框架提供的模板引擎,不仅能替换数据,也支持基本的条件判断、循环、自定函数等功能,类似一门“脚本”。
小提示:大多数情况下,我们写的C++程序用以高性能地、从各种来源(数据库、缓存、文件、网络等)、以各种花样(同步、异步)获取数据、处理数据。而HTML模板引擎在C++程序中以解释的方式运行,因此正常的做法是不要让一个模板引擎干太复杂的,毕竟,在C++这种“彪形大汉”的语言面,它就是个小孩子。
cookie支持
前端(浏览器)缓存支持
Redis 缓存支持
Session 支持
静态文件
模板文件更新检测及热加载
HTTP 客户端组件(已基于此实现微信扫码登录、阿里云短信的C++SDK,见下)
POST响应支持
文件上传、下载
访问限流
HTTPS
JSON
纯数据输出的API接口,与前端AJAX配合
框架全方式集成:(a) 基于源代码集成、(b) 基于动态库集成、(c) 基于静态库集成
常用编码转换(UTF-8、UCS、GBK、GB18030)
……
数据库访问
和nginx配合(实现负载均衡的快速横向扩展)
阿里短信云异步客户端
微信扫一扫登录异步客户端
基于OpenSSL的数据加密工具
常用字符串处理
……
大器 当前支持在Linux下环境编译。
为方便构建,大器的相关构建工具及外部信赖库,与“阿里云”(“腾讯云”、“华为云”、“七牛云”等国内云计算商类似)的Ubuntu 服务器版本保持基本同步。
因此,如果你有一台2019年或更新的云服务器,那么在其上构建大器,则所需的软件、信赖库等,只要Ubuntu仓库中存在,均只需使用“apt”指令从云厂商为该版本的Ubuntu提供的软件仓库拉取即可。当前仅“iconv”库需要手动下载编译。
当前国内各云计算提供商,均提供 Ubuntu Server 版本为 18.04 LTS 版本。以下内容均以 Ubuntu 18.04 为例,考虑日常开发不会直接使用Server版,因此严格讲,以下内容均假设系统环境为 Ubuntu 18.04 桌面版。
小提示-服务器与开发机的区别:
因此也假设你的开发机使用的是Ubuntu 桌面版 18.04 LTS 版本。以下涉及apt指令时,以开发机(桌面版)为例,因为如有需要,均带着“sudo ”前缀。当在服务器(Ubuntu Server)上编译,并且你使用的是默认的root用户,只需去掉 “sudo”前缀即可。例如:
开发机: sudo apt install git
服务器: apt install git
sudo apt install gcc g++
sudo apt install cmake
sudo apt install libboost-dev libboost-filesystem libboost-system
小提示-安装全部boost库:
boost中需要编译的库,大器只用到上述的“filesytem”和“system”。如果你想一次性安装所有boost库,可以使用:sudo apt install libboost-dev libboost-all-dev
sudo apt install openssl libssl-dev
我们使用汉字,而汉字有多种编码方案,因此,汉字的编码转换,是开发包括Web应用在内各种软件系统的常见需求。大器框架通过集成iconv库用以实现支持多国语言多种编码的转换功能。
先下载:https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.16.tar.gz
在Ubuntu图形界面中,双击该.gz文件,再点击其内.tar文件,解压后得到 “libiconv-1.16”文件夹。在终端进入该文件夹,依次输入以下三行,完成安装:
./configure --prefix=/usr/local
make
sudo make install
sudo ldconfig
通常你应该安装 git 工具,如果没有或不确定,请打开终端(Ctrl + Alt + T),按如下指令安装。
sudo apt install git
然后在本地新建一文件夹,命名为 daqi,打开终端进入该目录,执行如下指令,以从github上克隆大器项目(此站服务器在国外,较慢,为获得更快速速度,请往下看小提示):
git clone https://github.com/d2school/da4qi4.git
小提示-从国内服务器下载:
做为代替,也可以使用位于国内的的GITEE (开源中国)仓库(速度快很多)。仓库名是“da4qi4_public”:
git clone https://gitee.com/zhuangyan-stone/da4qi4_public.git
另:如果你就是不喜欢使用git,请进入 https://github.com/d2school/da4qi4 或 https://gitee.com/zhuangyan-stone/da4qi4_public 点击“Clone or download”按钮,然后选择“download / 下载”,得到 zip 压缩文件后,再于本地解压至前述的“daqi4”目录下。
无论从哪个仓库中克隆,还是手工下载解压,最终,你将在前述的“daqi”目录下,得到一个子目录“da4qi4”。大器项目的代码位于后者内,其内你应该能看到“src”、“include”等多个子目录。
如有余力,建议在以上两个网站均为本开源项目打个星 。
重要:以下假设大器源代码位于“daqi/da4qi4”目录下。
请在“daqi”之下(和“da4qi4”平级)的位置,新建一目录,名为“build”:
mkdir build
进入该当目录:
cd build
cmake -D_DAQI_TARGET_TYPE_=SHARED_LIB -DCMAKE_BUILD_TYPE=Release ../da4qi4/
将生成目标为“发行版(Release)”的大器“动态库(SHARED_LIB)”。
一切正常的看,将看到终端上输出“Generating done”等字样。其中更多内容中,包含有boost库的版本号、库所在路径,以及一行“~BUILD DAQI AS SHARED LIB~”字样以指示大器的编译形式(SHARED LIB)。
make
小提示:并行编译
如果你的电脑拥有多核CPU,并且内存足够大(至少8G),可以按如下方式并行编译(其中 -j 后面的数字,指明并行编译的核数,以下以四核数例):
make -j4
完成make之后,以上过程将在build目录内,得到“libda4qi4.so”;
如果是调试版,将得到 “libda4qi4_d.so”。如果是静态库,则扩展为“.a”。
现在,你可以使用你熟悉IDE(Code::Blocks、Qt Creator、CodeLite等)中,构建你的项目,然后以类型使用其它开发库的方式,添加大器的库文件(就是前一步构建所得的.so或.a文件),及大器的头文件。
下面以CMake的CMakefiles.txt为例:
cmake_minimum_required(VERSION 3.5)
project(hello_daqi LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -Wall")
# 此处设置大器项目目录
set(_DAQI_PROJECT_PATH_ "你的大器项目所在目录")
# 此处设置大器项目编译后得到的 .so 文件所在目录
set(_DAQI_LIBRARY_PATH_ "你的大器项目动态库所在目录")
include_directories(${_DAQI_PROJECT_PATH_})
include_directories(${_DAQI_PROJECT_PATH_}/include)
include_directories(${_DAQI_PROJECT_PATH_}/nlohmann_json/include/)
find_package(Boost 1.65.0 REQUIRED COMPONENTS filesystem system)
link_directories(${_DAQI_LIBRARY_PATH_})
link_libraries(da4qi4)
link_libraries(pthread)
link_libraries(ssl)
link_libraries(crypto)
link_libraries(boost_filesystem)
link_libraries(boost_system)
add_executable(hello_daqi main.cpp)
现在,你可以从之前“1.1 一个空转的Web Server”重新看起。
一个Web系统常用到缓存系统和数据库系统。大器框架对二者依赖情况如下:
显然,这已经不是本开源项目的自己的说明内容。不过,反正在Ubuntu Linux下安装Redis就一行话:
sudo apt install redis-server
这不仅会安装redis服务,而且会顺便在本机redis的命令行客户端,可以如下运行:
redis-cli
更多课程(视频课程、文字课程),请到 第2学堂查看。 谢谢。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。