优化 Web 推送框架:WebSocket、SSL、JSON、AngularJS 和 MySQL 的使用






4.79/5 (15投票s)
WebSocket、SSL、JSON、MySQL 全部结合在一起,通过推送框架构建了一个与 AngularJs Web 前端交互的 C++ 服务器。该应用程序展示了实现 CRUD 的简便性。
引言
如今,使用 C++ 作为 Web 应用程序的后端听起来可能有些奇怪。在本文中,我将向您展示一种技术组合,它会让您改变看法。通过演示一些常见场景的实现,读者可以判断将其扩展到实际应用案例的复杂程度。
目标
在这里,我们来定义我们的目标。首先,我们问:开发者在实现过程中会面临哪些常见场景?
答案必须很简单
- 必须定义和持久化一类项目 T1、T2 等。
- 给定一个类型 T,用户希望前端显示可用项目的列表
- 前端必须提供一种方式来显示一个表单,用于创建或编辑给定类型 T 的实例
- 给定一个 UI,我们需要一种方式让用户轻松查找给定类型 T 的项目。在这种情况下,一个常见的情况是,当一个类型 T1 与一个类型 T2 有外部关系时,意味着编辑 T1 的一个实例需要选择 T2 的一个实例。
即使有些人可能不同意这个答案,但我相信他们仍然会同意,这些场景足够重要,足以让他们欣赏任何简单且可扩展的实现方式。因此,我们的目标是证明实现这些场景非常容易。
AngularJs
如今的设备和操作系统太多了。使用 HTML5 和 Javascript 的 Web 应用程序正成为构建跨平台前端的最佳答案。随着 AngularJS 等新库的出现,Javascript 开发真正开始变得有意义。这里开发的演示应用程序,其前端部分将使用 AngularJS 构建的控件进行编码。
WebSocket
WebSocket 是一种基于 TCP 的协议,如今大多数浏览器都支持它。它允许网页与兼容的服务器建立全双工 TCP 连接。需要注意的是,这是一个比 Ajax 强大得多的概念。
AngularJs + WebSocket = ?
这两种技术的结合使得创建桌面级 UI 成为可能,其中大部分交互逻辑在客户端执行,而所有数据和命令都流向一个与访问应用程序的 Web 服务器完全无关的实时服务器。在我们这里的演示中,Web 服务器仅用于将应用程序机器上的资产下载到最终用户的设备。
推送框架
推送框架将用于构建将与前端交互的服务器,通过接收命令并发送回数据。
第 3 版优化了设计,使得可以
- 插入任何协议实现。(将协议逻辑分离到单独的抽象库:协议基础)
- 水平和垂直地插入任意数量的协议
- 推送框架服务器可以通过不同的端口进行监听。每个端口都可以指定不同的协议栈。这是水平方向。
- 对于每个端口,我们可以将不同的协议作为层叠在一起:这是垂直方向。
- 插入任何类型的序列化方法,而无需考虑协议选择。
为推送框架构建协议
在构建服务器应用程序之前,我们需要解决 3 个问题。首先,由于 Javascript 将打开 WebSocket 连接,因此我们需要服务器能够解码此协议。因此,我们需要 WebSocket 的协议层实现。其次,许多人需要通过 TLS 进行安全连接。因此,我们需要 TLS 的层实现。第三,由于 JSON 是 Javascript 默认的集成序列化方法,因此我们需要服务器能够以该方式接收结构化数据。因此,我们需要 JSON 序列化实现。
MySQL
对于那些希望开始新项目,但又不想在昂贵的 Oracle 或 SQL Server 许可上浪费资金的人来说,这个服务器是一个不错的选择。我们将使用 MySQL++ 库轻松与服务器通信。该库还实现了连接池,这对于应用程序速度非常有利。
DAO、DTO 和服务器端分页
MySQL++ 库极大地简化了与 MySQL 默认 API 的交互,用于发送 SQL 查询并在查询执行后检索数据。然而,如今许多人习惯了持久化框架,它们会自动完成与 DBMS 交互的所有工作。由于 C++ 中没有这样的框架,因此本文采用了代码生成的方法。
- 使用提供的生成器,您可以连接到现有的 MySQL 数据库并选择一个 SQL 表。
- 生成器创建 .h 和 .cpp 文件,其中包含表示您的 DAO 的 C++ 类。例如,如果您有一个名为 Person 的表,您将获得一个名为 Person 的 C++ 类,该类继承自一个名为 BaseDAO 的基对象。该类将所有表字段作为属性包含在内。它还包含getter和setter,并实现基类以下成员的虚拟逻辑:
- bool SaveChanges();
- bool GetFromDatabase(int _id);
- void SerializeToJson(Json::Value& jsonObjVal);
- void UpdateFromJson(Json::Value& jsonObjVal);
- bool deleteFromDB(int id);
- C++ 类拥有将自身转换为可发送到客户端的 DTO 的所有逻辑。
- 辅助类 View 使您可以轻松访问 SQL 视图。这用于返回 DTO 列表,这些 DTO 可以为客户端网格或查找控件提供数据,所有分页逻辑都在服务器端执行。
首先运行应用程序
让我们确保您能够成功获取应用程序代码、编译并运行它。
- 解压包
- 可选地将 WebApp 目录部署到 Web 服务器。它包含 Index.html,您可以导航到它,或本地打开它。
- 使用 Visual Studio 2012 打开 Demo.sln 解决方案文件(Demo.sln 位于 Example 目录下)
- 编译 Debug 版本
- 成功编译后,"Output" 目录将包含二进制文件。
- 服务器应用程序构建完成后,在启动它之前,我们需要设置数据库。
- 从 Internet 下载并设置 MySQL 服务器。可选地下载并设置 MySQL Workbench 以方便管理服务器。
- Database 目录包含一个 sql 脚本。使用该脚本创建数据库以及视图和表,并用数据填充它们。
- 在运行 DemoAngularJsServer.exe 之前,修改 DemoAngularJsServer.ini 以反映您环境中特定的数据库设置。
- 运行服务器。它将开始监听 81 端口。
- 打开 WebApp 起始页。您应该会看到一个项目网格。
- 选择网格中的一行并单击编辑。使用表单编辑项目的属性,然后单击保存。
解释发生的情况
- 页面打开时,AngularJs 控制器(HeaderCtrl)会请求与服务器建立 WebSocket 连接。
- 另外两个控制器 EmployeeListCtrl 和 CompanyListCtrl 将调用以检索项目列表(员工和公司)并在它们的网格中显示它们。
- 当网格初始化、表头排序或单击网格分页时,会进行 Javascript 回调,提供所有允许服务器访问 SQL 视图并返回有限行集的选项。
- 当单击“编辑选定项”时,将网格行的对象的 ID 发送到服务器以获取对象的完整详细信息。
- 当对象的完整详细信息收到后,它们将在 Web 表单中显示。
- 更改表单将更改绑定到它的 Javascript 对象。
- 单击保存会将修改后的对象发送到 C++ 服务器,服务器准备一个 DAO,使用传入的 JSON 对象中的更改更新它,然后调用 SaveToDatabase,其中包含负责调用数据库、执行 UPDATE 语句的生成代码。
支持新类型的复杂性。
添加新类型时,只需执行以下步骤
- 手动为新类型创建 MySQL 表。
- 执行 MySQL.Codegen.exe,输入数据库信息、表名,然后单击生成。
- 将生成的 .h 和 .cpp 类文件移动到 C++ 服务器项目。将它们添加到项目文件列表中。
- 修改 AngularClient::getDAOByName,以便它可以支持新类型。
- 如果需要显示项目列表或在客户端进行查找,请为该类型创建 SQL 视图。如果该类型与其他类型有外部关系,则可以通过丰富来自外部类型的列来改进视图。例如,每个员工行都有一个 companyId,但在显示员工列表时,我们也会显示公司名称。
- 在此阶段,服务器端只需要这些工作。第 4 步只需手动编写两行代码!
- 在客户端,添加一个新的所需视图和控制器,并将它们与应用程序(主要在 app.js 中)链接。
- 要添加与现有类型(公司和员工)类似的行为,您可以复制文件。真正改变的是以下内容
- 为了使网格正常工作,当它调用 WebsocketService.getView 时,请为它提供您创建的正确SQL 视图名称!
- 为了使表单正常工作,将您的控件绑定到完全相同的表字段名,然后无论何时控制器调用 WebsocketService.getItem 或 WebsocketService.deleteItem,请确保提供正确的类型名称!
- 如果类型有外部关系,您需要允许用户在编辑表单时设置这些关系。为此,请查看现有的 Employee Form 代码:所有需要的是
- 对于编辑,将关系字段绑定到 typeahead 控件
- 要显示编辑/显示对象中 ID 所代表的实体的名称,请在控制器开始时进行查找以检索外部对象,并使用适当的字段初始化 typeahead 显示文本。
从这些步骤,可以判断实现所有这些标准场景需要多少精力。
历史
这目前是本文的初稿。