C++20 高性能基础库--兰亭集库助力开发者构建高性能应用

简介: 这次分享的主题是《高性能基础库--兰亭集库助力开发者构建高性能应用》的实践经验。主要分为三个部分:1. 业务背景2. 雅兰亭库架构3. 业务优化

C++20 高性能基础库--兰亭集库助力开发者构建高性能应用

内容介绍

1. 业务背景

2. 雅兰亭库架构

3. 业务优化

 

01.业务背景

 

我隶属于阿里云基础软件部编辑团队,负责的 C++基础库名为兰亭集库,这是阿里云开源项目的一部分。兰亭集库的名字灵感来源于古代书法杰作《兰亭集序》。众所周知,《兰亭集序》是王羲之在会稽山与诗友们曲水流觞时所作,当时许多佳作被集结成册,王羲之为此书写了序言,从而诞生了著名的《兰亭集序》。我们团队希望效仿这一历史佳话,将优秀的 C++ 基础库汇集起来,形成一个卓越的集合,正如《兰亭集序》一样,因此得名兰亭集库。起初,“雅兰亭”这个名字中的“雅”字是不发音的,但随着人们习惯性的读法,它逐渐被接受,所以“雅兰亭”或“兰亭集库”都是可接受的称呼。

接下来简单介绍一下自己:我是一名热爱格律诗的程序员,我的三大爱好是 C++编程、写诗和羽毛球。前几天,我写了一首五言律诗,因为我来自杭州,西湖的美景让我灵感迸发。诗如下:日暖水寒烟,林林耀远山。轻舟摇碎影,短照入云间。十里清波咒,一红玉浪翻。白堤桥上柳,景美思居室,浮沉皆乐天。诗中提到了两个典故,首先是白堤。白堤是白居易在被贬至杭州时所建,白居易也被称为香山居士。当我看到白堤的美景时,不禁想起了这位伟大的诗人。白居易在被贬后仍为百姓造福,修建了白堤,使西湖更加美丽。无论境遇如何,他的性格始终乐观向上,因此,最后两句诗也是对白居易的赞美。

在我来北京之前,我一直在想北京的雪是否已经融化。昨晚在高铁上,我也即兴创作了几句诗,可能你们已经在群里看到了:北风寒彻骨,依旧踏征程。念念心怀起,明朝雪有无。今天我看到雪还在,所以这次旅行对我来说非常值得。我昨晚 12 点才到达,可能一两点才睡着,但我认为这是值得的。能够与大家共同度过一段愉快的编码时光,我感到非常开心。


02.雅兰亭库架构 

回到正题。雅兰亭库已经在 GitHub 上开源一年多,去年 10 月底正式发布。我将简要介绍它:雅兰亭是一个现代 C++基础库的集合,旨在为开发者提供一个高质量的基础库选择。我想强调的是,现代 C++之所以受到重视,是因为它利用了最新的 C++特性,包括 C++23 以及 C++20 和 C++17 的特性。这些特性使得团队能够实现有趣且酷炫的功能。无论是在易用性还是性能方面,都能得到显著提升,这也是团队的动力之一。另一个主要动机是,我们希望这些基础库能够帮助用户快速开发高性能的 C++ 应用程序。亚兰亭的核心目标是如果你想要开发高性能的应用程序,比如网络应用,那么直接使用即可。它包括了 HTTP、RPC 以及序列化等功能,团队几乎实现了全面覆盖。例如,二进制序列化使用 Strut Pack,其性能比 PROTOBUFFER 高出许多,在某些场景下甚至可以达到十倍以上的提升。此外,还支持 JSON、XML 和 YAO 等格式。亚兰亭的另一个特点是它仅包含头文件,意味着你只需下载代码并包含头文件即可使用,无需任何依赖。这也是团队所追求的目标之一,即易用性和开箱即用的便捷性。最后,亚兰亭是跨平台的,无论是 Mac、Linux 还是 Windows,都能运行无碍。因此,你可以看到我们在 GitHub CI 上的状态,团队测试了多个平台和不同的编译器,包括 Linux 系统上的 Clang、Mac 上的 Apple Clang 以及 Windows 上的 MSVC19。这意味着同一套代码可以在任何平台上运行。

接下来,我将向大家介绍如何编译雅兰亭。你们需要做的第一件事就是从 Git 上拉取代码。因此,请从 Git 上拉取雅兰亭的代码库。你们可以直接使用 Git 软件,打开它,然后执行 git clone 命令来获取雅兰亭的代码。如果你们已经拉取了代码,那么请打开 Visual Studio。在电脑上找到并打开 Visual Studio,然后打开微信开发者工具。在 Visual Studio 中,选择文件,打开 C Make 文件,并导航到雅兰亭的根目录,打开 Simon_List 文件。例如,你们可以观察我的操作:首先打开 C Make 工程,选择 simic_list 文件,这样就会生成工程。工程生成后,团队成员请等待下一步操作。当 Windows Studio 生成雅兰亭的工程目录时,可能需要等待几秒钟。配置完成后,我将选择我的编译环境,查看 source\bl\http\examples 工具,了解如何运行它。

image.png

image.png

选择一个示例文件,比如 example\chat_room.cpp,然后点击上面的“Debug”按钮进行编译。由于刚才正在编译,可能会出现卡顿,请稍等片刻。如果编译成功,你们会看到相应的提示。温度还没达到 SVC 的标准,但编译工作正在顺利进行。编译完成后,我们就可以开始运营了。编译速度确实很慢,让人有些焦虑。还在进行中,速度比较慢,但没关系,请耐心等待。一旦编译完成,我们就可以继续下一步。这台电脑的主频只有 2.2G,确实很低,而且只有双核,是一台相当老旧的电脑。

image.png

image.png

编译完成后,我会简单介绍一下代码,之后团队就可以开始一些有趣的工作了。举个例子,我之前的想法是创建一个聊天室,让所有人都能加入进来聊天,做一个简单的网页。实际上,大家只需要编写很少的代码。在开始之前,我先给大家展示如何创建一个文件服务器。比如,我有一个大文件需要下载,使用雅兰亭运行一下,你就可以实现目标了,几乎不需要编写代码。现在,让我们回到代码编译。大家只需让编译继续进行。以 Chat Room 为例,第一行代码我只是打印出当前路径,你们本地是没有这个路径的。你们可以稍后添加代码,或者现在就加上去。添加后,运行代码,查看执行进程的当前目录,因为这个目录对团队后面的工作很有用。如果团队需要创建文件服务器,你们需要将文件放入执行目录中,这样才能够实现下载。所以,请添加这样一行代码来打印当前目录,你们可以看一下当前目录在哪里。使用 fs.currentPath,将输出显示出来就可以了。

现在开始编译 C++程序,这确实需要一些时间。编译完成后,运行程序就可以看到目录了。目录已经查看完毕。接下来,团队将讨论如何编写文件服务器。首先,团队可以指定服务器的线程数。第一个参数就是线程数,即你希望有多少个线程提供服务。我这里默认选择的是一个,但你可以根据你的 CPU 核心数来选择,比如八核或 32 核,只需填入相应的数字即可。第二个参数是服务器要监听的端口,这里设置为监听 9001 端口。之后下载操作都是通过这个端口进行的,这是最基本的配置。一行代码就可以完成设置。

有了代码之后,接下来的第二、第三行代码是关键。这行代码的含义是设置静态资源的目录,即我要指定文件应该放在哪个目录下。如果这两个参数都留空,那么文件将放在执行目录中。团队刚才已经查看了第一行输出的目录,接下来,团队将把静态文件拷贝到该目录中,然后启动服务器就完成了。

演示一下。比如,现在你设置了一个文件服务器,你只需要做的一件事就是设置好静态资源的目录并启动它。如果你希望更灵活一些,你可以将这两个路径参数从 main 函数的参数中设置进去。因此,理论上,你可以零代码实现一个文件服务器,只需配置这两个路径。确实是个好问题。让我来详细解释一下。假设你需要编写一个文件服务器程序,首先,你必须指定一个基础的物理磁盘路径,这通常被称为基础目录,即 base 目录。这是存放静态资源的根目录。然而,团队可能还有额外的需求,比如希望添加一个虚拟目录。第一个参数就是这个虚拟目录的名称。这样做是因为,如果你直接暴露了物理磁盘路径,可能会遭到基于目录的攻击。因此,通过设置一个虚拟目录,你可以有效地隐藏实际的物理路径。虚拟目录与物理目录是相互映射的。第一个参数填写虚拟目录的名称,第二个参数填写实际资源所在的物理路径。为了简化说明,我这里直接使用当前目录作为示例。启动服务器后,团队成员可以查看资源。例如,如果我在当前目录下的一个名为 Example 的文件夹中,那么我将展示 Example 文件夹中的资源。大家也可以在自己的 Example 文件夹下查看,比如在 Excel 目录下,你会发现一些有趣的资源。我建议使用 BatchGIGBH 命令来查看,这样会更方便。给大家展示一下,我的当前执行文件目录中包含了许多文件,这些文件都是静态资源。例如,这里有一个 MP4 视频文件和一个 HTML 静态页面。稍后,我会向大家展示如何下载 MP4 视频文件。理论上,只需输入团队的 IP 地址和端口,再加上文件名,就可以完成下载。让我来演示一下。此外,还有一个方法,我好奇你们是否已经尝试过使用 CR 工具。请回复一下,确认一下。

现在,我们的目标是下载之前名为 testmp4 和 test2mp4 的文件。首先,我们需要删除之前的文件。接下来,请跟随我的步骤操作。首先,将应用地址填入。地址填写完毕后,我们希望下载的是通过 HTTP9001 端口提供的 test.mp4 文件。填好命令后,执行它。大家可以看到,文件已经下载完成。这样,我们无需编写任何代码,就能实现文件服务器的功能。你们也可以尝试一下,只需使用 Curl 命令,输入服务端静态资源的地址即可。检查一下文件是否下载完成,如果完成了,我们再下载另一个文件,即 test2.mp4。然后,团队可以验证一下文件是否与服务端的文件一致。如果能正常播放,那就说明文件是完好无损的,与服务器上的完全一样。你们中如果有已经下载完成的文件,可以尝试运行一下,看看是否能正常播放。

接下来,团队需要了解的是,我们不仅可以使用工具下载,还可以通过浏览器进行下载。请在浏览器中输入相同的网址,就像之前一样。看,我已经下载完成了,你们也完成了吗?如果还没有,那可能是因为它遵循了标准的 APP 协议。无论是通过 KR、Wget 还是其他工具下载,或是通过浏览器下载,结果都是一样的。想象一下,如果你们想要将一些资源放到云端供人下载,使用雅兰题库是不是只需一行代码就能完成呢?现在检查一下你们的程序是否已经运行起来。请确认当前目录在哪里,当前目录就是这个。因为你们本地没有文件,所以你们需要下载 Index。这个环节有没有 Index 呢?在 1example 目录下有一个 client.html 文件。请将这个文件拷贝到你们当前的目录下,也就是刚才启动的目录里面。resource 目录下有一个 source 文件夹,里面有一个 CORE TP example 文件夹,里面有一个 Client.Html 文件。这也是一个网页,团队的网页也是一个静态资源。

所以,只要将文件放入其中,在浏览器中输入 HTML 地址,你们就能看到内容了。你们也可以直接输入目录地址进行下载。你们目录下有静态资源,就这几个文件,只需要下载 simon_list.txt 文件。将文件名复制粘贴到浏览器地址栏中,确保文件名以.txt 结尾。需要查看一下代码,确认是否与我的代码有所不同。请检查一下 Single Start 是否正确,怀疑可能是执行目录的问题。请确保启动时能正确显示目录,然后将文件放置到指定位置。现在,让我们来看一下 Output example。一个弹幕 quaeTP,你可以下载一个 EX 文件,或者随意选择一个文件。只需在目录中选择一个文件即可。因为上面没有文件,而我这边的路径显示你的执行文件并不相同。请查看一下,你的执行文件路径是否正确。你需要将文件拷贝到进程运行的目录中,第一行应该输出目录路径。请确认你的目录是否与我的不同,因为环境不同,你需要查看本地第一行的目录,里面有哪些目录和文件,直接进入盘符查看。请下载并检查目录,确认是否是 Out 目录。对,Out Build 与 Example Output。

可以在里面随意选择一个文件,输入文件名,查看一下。直接放到 9001 后面就可以了。下载完成后,说明静态文件服务器已经配置完成。在完成视频下载功能后,团队接下来面临的问题是:如果我拥有大量视频资源,该如何建立一个视频网站?毕竟,下载文件只是基础功能之一。接下来,团队将着手打造视频网站。实际上,一个 HTML 页面就足以实现这一目标。我将打开一个本地 HTML 页面,供各位参考。由于你们本地可能没有视频文件,我在思考如何将视频文件分享给你们。

或许,你们可以通过访问我的 IP 地址来获取视频。让我们试一试,我将我的 IP 地址拼接起来:你们只需在浏览器中输入我的 IP 加上 9001 端口,然后下载 test.mp4 文件到你们的执行文件目录即可。192.168.110.147:9001,拼接起来是这样的。但请注意,我们处于同一个局域网中。如果你们不在同一个局域网,那么这个方法就行不通了。不过,我有一个解决方案:我可以将文件上传到我的云端服务器上。

这样一来,你们就可以轻松访问并下载了。我的云端服务器 IP 地址47.96.38.82:9001,尝试访问并下载 test.mp4 文件。你们可以通过 Curl 命令来检查这个地址。由于我设置了限速,默认下载速度为 100KB/s。当然,这个速度是可以调整的,可以在服务器设置中进行配置。我将展示如何设置每次下载的大小。稍等一下,我将调整速度限制,以便更快地传输文件。我将 Transfer 的 Size 设置为 4MB,意味着每次可以传输 4MB 的文件,当然这也受限于网络速度和时间。由于实际带宽不同,我还需要调整一个参数。由于下载的人数较多,我将线程数设置为 10 个,这样即使我的机器是双核的,我也希望能够尽可能地并行处理,充分利用 CPU 资源。请大家参考我的 Get It 修改,首先将线程数设置为类似我自己的配置。

如果可能的话,同时拥有十个并发连接,这相当于我团队内部的线程模型,每个连接对应一个线程,或者一个线程对应多个连接。如果访问人数众多,那么一个线程可能会对应 N 个连接。目前,你们可以看到已经有六个人在连接上了,我的日志显示“new collection coming in”,现在网速大约是 100K 左右,有 90 多 K。因为服务器是双核的,总带宽大约是三兆。我估计,几分钟后,显示的总带宽将是十几兆。现在,你们看到下载量了吗?AHHTTP 显示 47.96961.96.38 点 82,看看我下载了多少。

如果我要做一个视频网站,没有视频内容会有些遗憾。不过没关系,你们也可以看看我做的内容,或者你们也可以自己录制视频。最好的办法是,在本地找一个 MP4 文件,如果可以的话,就不用下载公网资源了。打开摄像头,自己录制一个十秒或二十秒的视频,这个方案是可行的。使用 Windows 加 G 命令行录制视频,十秒钟或二十秒就足够了。接下来,将视频文件命名为 Test.Mp4,并将其放到执行目录下。同时,你需要将 Index.Html 文件拷贝到执行目录下。注意,团队 Chat Room.CPP 文件目录中有一个 Index.Html 页面,也需要拷贝到你的执行目录中。拷贝到 Current Pass 目录中,之后重启你的服务器。因为重启的目的是加载新添加的资源,由于团队使用的是静态文件服务器,它不是动态的,所以添加文件后需要重启服务器以加载资源。

在 IP 地址 127.0.0.1 的端口下,HTML 文件名为 index.html,端口 127 实际上是一个本地主机地址。团队正在监听这个地址,它本质上也是一个日志主机。请确保你已经将文件放入了正确的目录。例如,在硕士目录下的 Chat Room CPP 目录中,只有一个客户端可以查看。那么,我将把相关目录放到服务器上,以便快速下载。你需要下载的 HTML 文件就在那里。请确保你的目录服务已经启动。如果你在当前目录下找不到文件,请检查一下。例如,查看 47.96.38.82 端口上的 Index.Html 文件是否正确。如果找不到,我会检查一下我的文件。确认之后,我会将文件拷贝过来。请稍等片刻,然后再次尝试下载。如果服务尚未启动,请稍等,我会确认一下。哦,看来我把文件放错了位置,真是不好意思。

现在我已经将文件放到了正确的位置。请再次尝试下载。如果你已经下载了 HTML 文件,你可以通过网页或 KR 通过 QQ 下载。输入地址 47.96.38.82,然后下载 Index.Html 文件。请检查是否一切设置正确。IP 地址 47.96.38.82 是正确的。现在,你可以在浏览器中输入该地址,然后重启你的服务。一旦文件放置妥当,重启后你就能看到你的视频网站了。如果你已经下载了所需的文件,请确保将它们拷贝到当前路径下,包括你录制的视频。如果你已经完成了这些步骤,那么你的视频网站应该已经可以正常工作了。请确保你的服务已经重启,index.html 页面也已经拷贝到了正确的位置。如果你需要通过 curl 命令下载文件,那是可以的。

没有 HTML,这些内容也无法通过转接头来实现。那么,你们现在能本地观看《三国演义》吗?首先,你需要确保 index.html 给你的 ATS、N、P4 都在你当前执行文件目录下。然后,你再刷新一下就可以了。如果还是不正常,那说明文件可能也有问题。你检查一下,你的 mp4 文件名是不是 test.mp4?因为我在页面上写死了,就是 test.mp4。如果没有它,当然会显示灰色。这里录制的视频都没有了。输出的也是目录吗?需要服务重启一下。因为它是静态文件,静态文件目录,刷新一下,你是 127,输入个 127(不确定是 Logo 有没有问题)。看一下 Output 的 Excel 科云,看资源的目录,看一下 Output Example,Test.Mp4,应该没有问题。Index 直接执行一下,EXC 放一起是林子,把 EXCE 拷过去。

执行文件运行了吗?没有执行就先关掉它。感觉是之前有服务没停?有没有可能不是 1EX1?检查一下看看是不是都执行了,是不是重复的?那应该没有。那如果再不行的话,那就等后面再看。他访问看不到 test.mp4 的文件。那现在有至少有一半的同学已经完成了。团队也讲一下 ETM 页面。网站它到底是怎么去播放视频的。

其实很简单,大家看看HM页面,它的构造相当简洁,全部基于标准的HTML标签,特别是HTML5中的Video标签。Video标签里设置了视频的长宽尺寸以及资源地址。当你在浏览器中输入网址加上Test.Mp4时,它会自动访问你的静态服务器,随后视频就能播放了。整个代码不过寥寥几行HTML,非常简洁。比如说我,运行后刷新页面,点击播放,视频就开始了。关于操作方式,基本上就是编写代码。例如,如果我想实现画中画播放或暂停功能,原理都是一样的。没错,这样一来,一个视频播放网站就几乎完成了,不是吗?你基本上只需要编写一个静态页面,再写一个脚本,任务就大功告成了。团队通过雅兰亭编写的静态文件服务器,几乎可以实现零代码操作,这一点大家都能感受到。你可以通过W或二维码下载,或者直接在浏览器中下载。

接下来,团队要做的第二件事是创建一个聊天室。那么,聊天室是如何实现的呢?首先,团队肯定会通过WebSocket技术来实现聊天功能。具体来说,当你登录聊天室时,需要输入一个用户名,也就是你的昵称。输入昵称后,聊天室会显示所有在线人员的列表。然后你就可以开始发送消息了。每当有人发送消息,系统就会向其他所有人广播,确保所有人都能看到这条消息。接下来,团队可以让大家看看部署在云端的聊天室,大家可以进入体验一下。注意,代码中的单引号需要删除,之前没删的也不用管,因为我故意加上的。这样一来,编译运行就可以了。

我已经准备好了,实际上代码已经准备好了,就在GitHub上,大家可以看一下效果。哦,选错了,不是这个地方,看错了,我要停下来。不是HTML文件,是本地的服务器地址。我选一个远程的,看来远程页面已经在服务器上准备好了。这样一来,大家只需要输入网址就可以了。对,这里我随便选个名字,如果默认的话,我也随便生成一个。这就是一个简单的聊天室。大家访问47.96.38.82:9001/client.html(注意:这里的IP地址和端口号仅为示例,实际访问时请使用提供的正确地址),如果没有的话,就是Client.Html,这是从远程服务器拉到本地的页面。我看到已经有几个同学加入了,四个,你看,已经有四个人在线了。继续加入,对,你看,别人是不是都能收到消息?我超厉害的,“超级厉害的小超”上线了。现在有五个同学,团队应该不止五个,有十个了?继续加入,六个了。大家登录进去之后,我稍后会给大家演示如何实现聊天室功能,其实只需要写一点点代码就可以了。

好了,谁在上线,稍微等一下,我看看,把游戏关掉,我看看情况。哦,我看到有人下线了,看到没有?现在只剩六个同学了,不对,是五个同学了。

介绍相关功能。当你首次进入聊天室时,我的在线用户列表需要更新。一旦你登录,我也必须更新,因为你已上线。同样,当你退出时,我也要更新,因为你已下线。此外,每当聊天室中的任何成员发送消息时,我需要通过  WebSocket  向所有人广播。这是一个经典的聊天室实现方式,虽然它非常基础且未经过美化,因为我们的团队仅旨在进行一个简单的演示,以展示其基本功能。好的,团队,让我们再次回顾一下。哦,大家还没玩够吗?玩够了的话,团队将开始讲解代码。好的,团队,让我们回到代码上来。团队,我想一点一点地查看代码。首先,聊天室属于业务逻辑的一部分。如果你想实现功能,比如希望使用雅兰亭的 AHP 服务提供一个专门的服务,你需要编写自己的业务逻辑。

那么,如何使用雅兰亭编写业务逻辑呢?以 App Store 为例,你需要指定你的 App 请求是 POST 还是 GET。因此,接下来你需要注册你的业务逻辑,具体来说就是你的处理逻辑,也就是函数。例如,团队聊天室的核心逻辑就位于代码中。首先,你需要指定你的 HTTP 请求是 GET 还是 POST。由于 WebSocket  使用的是 GET 请求,这符合标准的 HTTP 协议。其次,你需要指定你的 URL。我这里设计的只是一个根目录,但你也可以输入其他路径,比如输入一个特定的 URL,如“/ws”或任意的业务逻辑 URL,如“/index.html”。URL 中“/”后面的部分称为“pass”。团队的 hp 库会根据“pass”路由到相应的业务函数。因此,团队主要关注的是业务逻辑的实现。接下来,我将为大家讲解业务逻辑如何实现。其实,它的逻辑很简单,就是根据消息类型进行不同的处理。

具体来说,有三种消息类型:一种是“Login”,即登录时需要刷新用户列表;另一种是“Logout”,即登出时需要下线用户列表。这两种逻辑基本上是相似的。因此,你可以将它纳入一个分支中进行处理。另外,还有一个消息需要说明,即逻辑上,当用户发送消息时,团队将其类型命名为 Useruser。首先,团队应从最简单的 User 逻辑开始。当你的前端发送一个消息时,通讯格式是一个 JSON 字符串。大家都知道,JSON 字符串包含两个字段:Type 和 Content。Type 字段表示消息类型,这是由你自定义的。我这边定义的是,当你发送消息时,我将 Type 命名为 User,而你的内容则由两个字段组成。我将其定义为一个结构体,称为Message。这个结构体也有两个字段,即 Type 和 Content。例如,如果你发送了消息"AABBB",那么它就是一个简单的 JSON 字符串。

当服务端接收到前端发送的请求后,我的第一步是进行反序列化。因为这是一个 JSON 字符串,我将其反序列化到我的对象中。由于通讯协议是我确定的,它包含两个字段:Type 和 Content。这一步,我将其反序列化到结构体中,解析完成后,我接下来判断消息是否为 User 类型。User 类型意味着消息是由用户发送的,需要进行处理。首先,我需要获取当前所有客户端的列表,因为我的聊天室可能有十个人,也可能有一百个人。因此,我的代码逻辑是使用一个 Collection Map 来记录所有已连接的客户端。连接完成后,我接下来需要广播消息。你可以看到,广播消息也是一个 JSON 字符串。

广播消息的内容略有不同,因为它不仅需要一个 Type 字段,还需要一个 From 字段,即消息是由谁发送的,可能是 A,也可能是 B。因此,我需要将发送者的消息类型和内容序列化为 JSON 字符串。序列化完成后,接下来就是广播消息了。

广播消息简单首先,第一个参数是包含所有连接的列表,第二个参数是我刚才用 JSON 格式绘制的一个字符串。我需要将消息广播给所有连接,如何做到呢?只需一个简单的 For 循环。遍历所有的连接,获取每个连接后,直接通过 WebSocket 发送消息。这确实简单,这就是广播的逻辑。接下来,让我们讨论登录和登出的逻辑。当收到登录或登出消息时,团队需要执行一些操作。如果是登录,我首先需要获取用户名,然后将其插入到连接列表中。连接列表由连接对象的指针组成,其名字是唯一的,因为每个用户都有一个用户名,所以连接和名字是唯一绑定的。

绑定之后,我需要获取用户列表,因为 Map 已经完成,接下来我要将用户列表发送给客户端,即前端。获取用户列表也很简单,因为我是从 Map 中获取的,只需将 Map 的值放入一个 Vector 中。然后,我将 Vector 放入 Logoutinfo 中,并将 JSON 字符串广播出去,告诉客户端更新用户列表。这样,如果用户已经下线,列表就会更新。同样的,如果是登出,团队需要将其从列表中移除,列表就会减少。如果是登录,列表就会增加。其他逻辑都是一样的,逻辑就是这么简单。实际上,代码总共只有几十行,不到 100 行。你们可以稍后查看代码示例,都可以运行起来,也可以再看一下前端逻辑。前端逻辑是这样编写的:一个脚本,一个 HTML 脚本。例如,最初需要输入用户名,输入用户名后,需要连接到服务器。连接服务器后,WebSocket 会有一个 Onopen 事件,在该事件中将用户名和类型发送到服务器,即 Login 事件。服务器刚才已经讲过,如果向服务器发送 Login 消息,表示谁登录了。服务器需要刷新列表,并将列表广播给所有人,让他们更新左侧的用户列表栏。如果发送消息,服务器会处理并回应消息。例如,如果我给你回了一个广播消息,收到广播消息时,我会在浏览器中更新是谁在说话。逻辑很简单,登录和登出只是刷新页面而已。前端页面也很简单,无非是发送消息。将用户和内容制作成 JSON 字符串,发送出去即可。代码也很简单。

一个聊天室,你们可以在本地运行它,可以在本地输入地址,查看 127.0.0.1/client.html 页面是否有反应。你们可以在本地运行 CLI 聊天室程序,运行起来了吗?自己尝试一下,在本地尝试一下就行了。启动你的聊天室,你赢钱了!请展示你的当前目录。你可能需要在聊天室中输入命令。不需要添加新行,只需输入 "cout",即 "std::cout"。将这行代码输入后,你的 HTML 页面就可以放置在目录下了。有人已经成功运行了吗?如果找不到,就复制过去。因为 HTML 文件与聊天室位于同一位置,你需要将其复制到执行文件的目录中。如果有问题,我可以帮你检查。只需输入一个名字,然后点击确定。你已经进来了!现在输入你的本地地址,将 "1127" 改为 "127"。

首先确保你的本地有 "CLI.html" 文件。如果没有,请复制过去。它的路径就在聊天室目录下。再打开一个聊天室,命名为 "brother"。复制 "source_copy_example" 中的 "client.html" 文件,将其复制到你刚才的执行文件目录中。如果你遇到卡顿,请右键点击。如果运行不起来,可能是因为点击左键会阻塞控制台,这是 M4VC 的一个特性,而不是 bug。输入 "client",然后点击确定。现在你可以发送消息了。你可以刷新页面,再次输入你想要的名字。如果你想让别人连接,只需告诉他你的 IP 地址,他就能连接了。尝试一下,看看是否能连接成功。在理论上,局域网内应该可以连接。如果你已经搞定,发送消息 "OK"。我看到已经有几个人成功运行了聊天室,甚至在 LINUX 上也可以。部署到云端也是同样的方法,因为我们的团队是跨平台的,你可以随意选择任何平台进行操作。,你的学习速度很快,既然你有 CI 的基础,那么我想分享一些经验。

实际上,当你需要开发服务端时,只要合理利用库,C++的开发效率同样可以很高。我的代码就是基于一个菲律宾开发者在 GitHub 上的项目,其服务端实现仅包含 300 多行代码,核心代码甚至不到 100 行,包括头文件在内也只有 132 行。因此,C++的开发效率确实可以很高。我希望传达的理念是,许多人认为 C++难以掌握,但实际上,问题往往出在方法不对。对于编写库的人来说,对于使用者来说,也是如此。西亚最大的问题就是缺乏好用的技术。这就是我正在做的事情,我称之为兰亭集库,就像兰亭集序中的诗一样美丽。你可以随意使用,如果库中没有你需要的功能,你可以向我提出需求,我会帮你实现。这是否让你心动了呢?团队的 GitHub 上经常有人提出新功能,比如最近有人提出希望 KET 能支持扩展功能,我说可以,但需要给我一些时间。

 

03.业务优化 

另外,我还要介绍一点,现代 C++能够让团队的逻辑代码变得如此简洁。首先,网络方面我使用了携程(协程),C++20 中的携程功能。你们可以查看携程的实现,我稍后会展示给你们看。携程(协程)的使用,使得代码更加简洁,效率更高。携程(协程)类似于 Go 语言或其他一些 Python 语言中的 Await 关键字。让我稍微解释一下携程(协程)的作用。哦,我差点忘了说,服务端是高性能的,因为它采用纯异步实现,不会阻塞当前线程。由于使用了携程(协程),许多服务端,包括 Node.js,或者其他的同步单线程服务端,效率都相对较低。当我使用携程(协程)等待 WebSocket 消息时,我不会阻塞线程。携程(协程)会挂起当前函数,返回给调用者,调用者可以继续执行其他任务,而不会阻塞当前线程。因此,这是一个异步过程。当未来某个时刻,我收到消息,比如登录消息,我的携程(协程)就会返回并继续执行。然后我会继续执行到广播阶段,完成后再次返回。整个过程没有任何阻塞。这就是 C++协程的一个显著优势:代码可以以同步方式编写。想象一下,如果团队需要使用传统方式编写回调函数,那将多么繁琐。然而,在 C++20 中,我们进行了一项革新:你无需再编写回调函数。以同步逻辑编写代码不仅易于理解,而且编写起来也更加轻松,不是吗?因此,C++20 赋予了我们强大的能力——这不仅仅是语法糖。这不是简单的语法糖,而是一个真正的语法增强。虽然协程的概念确实历史悠久,它在 70 年代就已经存在,但直到 2020 年才被纳入 C++标准。

在此之前,虽然也有协程的实现,但它们并未标准化。现在,协程已经成为标准化的一部分。基于 C++协程,团队封装了一个名为 think simple 的协程库,它已经被集成到团队的 CORE TP server 中。因此,你现在可以非常轻松地编写代码,从头到尾按照同步逻辑编写即可。当你需要调用异步操作时,只需添加一个 call wait 即可。因此,我希望团队的雅兰亭库能够帮助你们快速构建高性能应用。好了,今天还有其他问题吗?今天的工作坊应该已经完成了两个目标:视频网站我已经看到你们上传的帅哥靓女视频了,虽然没有分享给大家,有些遗憾,但还有第二个目标——团队的聊天室。大家都展示了各种各样的才艺,我也在里面看到了,这是一件非常开心的事情。

所以,团队工作坊的目标已经完成:使用一个好的 C++基础库确实可以事半功倍,感觉完全不一样。跟你们以前对 C++的理解相比,现在是不是觉得简单多了?你为什么还要使用性能较低的 Python 呢?介绍那些性能低下的框架有什么好处呢?这给了我新的认识,这是我最大的想法。好了,还有其他问题吗?第二集,其他老问题,嗯,好的。后来,我给你看我的技能,作为一个新的课题。嗯,好的。你是统信那边的吗?哦,是科大讯飞的。是这样的,首先因为雅兰提供的是现代 C++,它与旧的 C++98 标准不同。你可以认为 C++11 之后的标准就是现代 C++,也称为 modern C++。它加入了大量的新特性,不仅仅是语法糖,还有很多创新的东西。总之,目标就是让 C++变得越来越简单,越来越好用。这是 C++不断迭代的动力。

或许你正面临一个难题,那就是如何跟上最新的编程标准。确实,使用最新标准的感觉很棒,我也渴望尝试,但随之而来的负担也不轻。通常,有几种策略可以应对。简单来说,并不容易,但并非不可能。那么,团队应如何以最简单的方式应对呢?首先,你需要升级你的编译器。假设你当前使用的是 4.8.9 版本,那么直接升级到 GCC 10.2。不要担心版本间的巨大差异,直接进行升级。然后,重新编译你的旧代码。当然,你会遇到编译错误,这是正常的。因为某些特性可能在新版本中被移除,但这些通常都是小问题,编译错误也容易定位。你需要做的是查找并替换那些在旧版本中使用但在新版本中已被移除的特性。

可能需要两个阶段来完成这个过程。第一阶段是使用新标准重新编译你的代码,并确保所有的 CI 测试都能通过。如果一切顺利,那么你就可以开始使用新标准了。另外,新标准已经尽可能地实现了向后兼容,这意味着你以前的代码几乎不需要改动,只需修正一些编译错误。如果你之前使用的特性不多,可能只需修改几个文件中的几行代码,你就可以开始使用 C++20 了。这是第一步。第二步可能会稍微复杂一些。在新项目中,你可以尝试使用 C++20,因为如果你想要在旧项目中使用新特性,可能会遇到接口的不兼容问题。

举个例子,我之前可能只是定义了一个普通的函数,并非是延迟加载(lazy)的。这样一来,结果就发生了变化。因此,如果你修改了旧代码,可能会导致问题,比如原有的用户接口不再兼容。那么,我们该如何应对呢?一种方法是让下游和上游的代码都进行相应的修改。我的团队经常遇到这样的问题:有些老旧的业务流程运行得非常好,性能、内存使用和并发处理都很出色,但它们遵循的是旧的标准。面对这种情况,我们通常会专门派出一个团队进行分析,确保从客户端到服务端的代码都能同步更新。这是一个解决方案。

另一个方案是暂时保持旧代码不变,在新项目中采用新标准,并逐步淘汰旧代码。这将是一个漫长的过程。确实,C++存在一些问题,因为它的历史包袱较重,团队也经常遇到这类问题。因此,如果你想从旧标准升级到新标准,可能需要按季度来计算时间,这取决于你的业务规模。业务规模越大,所需时间越长。团队内部的一个案例是,有人花了大半年时间从 4.8 升级到 4.42107 版本,仅仅升级标准就花了这么长时间,因为需要修复许多用例,可能以前有些未定义的行为或不规范的写法,在新标准下都成了问题。修复这些问题时,你需要运行持续集成(CI)和压力测试,这个过程是漫长的。团队追求的是稳定性,所以升级不会那么快。最好的情况是,新项目直接采用新标准,没有历史包袱。还有,有没有更简单的办法呢?当然有。无论是应用程序(app)还是远程过程调用(RPC),或者是网络服务,都很简单。你只需要启动团队的代码服务器,编写一个简单的 echo 服务,返回"hello world",并确保线程数与请求数相匹配。然后,使用一个名为 WRK 的压测工具来测试你的服务器。将你的服务器与团队的服务器进行对比,看看哪一个性能更优。

同时,观察你的 CPU 使用情况。例如,我曾经对旧服务器进行压测,CPU 使用率达到了 60%,而 QPS 大约是 10 万;而团队的服务器 CPU 使用率只有 30%,QPS 却能达到 50 万。显然,你会选择后者。而且,我的代码量只有原来的 1/5,我对此非常有信心。如果你之前使用的是 BRPC 或 GRPC,那么差异会更加明显。我曾经做过一个测试,GRPC 的 QPS 非常低,可能不到 10 万,而团队的 RPC 服务器,在 96 核的服务器上测试,QPS 可以达到 400 万,大约是前者的十倍。所以,你可以使用第三方压测工具,在相同的环境下进行测试,并通过结果来进行对比。当然,你也可以顺便检查一下你的原始业务逻辑代码行数,以及现在需要编写多少行代码,这是一个非常简单的过程。

在我看来,是否升级实际上取决于你们领导是否重视性能。但最重要的是,如果你在升级过程中遇到问题,关键在于他是否关心。比如,如果你发现性能瓶颈或痛点,团队通常会主动找到我。假设你告诉我,我的代码简洁、性能良好,并发数高,那么你可能不需要更换。但如果你某一天意识到性能需要优化,你就会迫切地要求更换,甚至会不断催促团队进行更换。因此,你必须在遇到性能瓶颈时提出问题,这样你可能很快就能升级成功。团队的另一个技能是,如果没有痛点,你就无法推动升级。举个例子,团队有一个训练化库叫做 STARTPACK。曾经有一个业务方在 UC 浏览器的广告索引中使用了它。他们之前使用的是 pro buffer,将数据存储到数据库中进行索引。日 PV 达到 300 亿时,大家可能认为从文件中检索索引很快,但在达到 300 亿日 PV 时,它就成为了瓶颈。

这时,团队提出了两个解决方案:首先,团队的性能比 Pro Buffer 高出四五倍;其次,他们希望在提升性能的同时压缩 Binary Size。原来使用 Pro Buffer 时,内存占用大约为 16-17Gb,而使用团队的 Fat Pack 后,内存降至大约 7-8GB,性能也得到了提升。他们对此非常满意。因此,你必须发现瓶颈才能推动团队。这是团队的经验。如果你主动向他人推荐,他们可能会说:“我有我自己的东西,万一引入 Bug 或系统崩溃,谁来负责?”而且,很多人会以性能或网络问题为由拒绝。所以,如果没有问题,不要去推动。当遇到痛点时,再深入挖掘并推动。这是团队的经验,甚至他们会主动找到你去推动。关于 a Soul,它实际上是 Boost 的一部分,但它有独立的仓库。作者先有了 a Soul,然后将其移植到 Boost 中。

因此,团队依赖的是独立版的 a Soul。这里我想补充一点,a Soul 本身并不大,可能只有几兆字节。它最大的优点是跨平台,封装了 LINUX、Mac 和 Windows 的接口,统一了接口。如果你想编写跨平台程序,a Soul 是不二之选。很多时候,人们认为自己编写的代码性能会更好,但这并不总是正确的。你可以自己编写一个 import 的应用服务器,并与基于 a Soul 的应用服务器进行基准测试。如果你有信心打败它,那么你很厉害;如果你没有打败它,那么为什么不使用现成的呢?关键是它是一个经过验证多年的工业级库,你可以放心使用。

而且,它还能节省很多时间。在迁移方面,团队开发了一个名为“倚天”的迁移工具。这个工具的动机是将许多老业务从 X86 架构迁移到 ARM 架构上。X86 架构与 ARM 架构不同,例如,在 C++中,默认的内存序在 X86 上是强内存序,在 ARM 上是弱内存序。如果你之前在 X86 上将代码当作强内存序来编写,那么移植到 ARM 上时,肯定会出现线程安全问题。团队推出的工具会检查所有与内存序相关的代码,提示你注意。但除此之外的大多数场景,只要你使用标准 C++编写,你的行为是确定的,因为它符合标准。例如,当你调用一个函数并期望返回文件路径时,它会返回文件路径或错误,不会因为你是 X86 还是 ARM 而有所不同。还有,如果你之前调用的是系统 API,那么就没有问题,只需调用标准的 C++库即可。

实际上,问题并不大。关于静态分析,有很多工具可以使用。例如,团队有一个名为 a Son 的工具,它非常有用。a Son 可以帮助你检测内存泄漏和溢出。团队通常会开启 a Son,这样就能及时发现并检查内存泄漏。例如,如果你在主函数中使用 While 循环,并且在连接关闭时没有跳出循环,那么就会发生内存泄漏。因此,团队要确保携程不会泄漏。有时候,团队在编写代码逻辑时会忘记,这时开启 a Son 就会告诉你哪里发生了泄漏,需要检查。这就是 a Son 的基本逻辑。基本上,a Son 可以满足需求。

理论上,团队的代码没有内存泄漏,也没有越界问题。对于 Windows,我不太熟悉,但听说 Windows m s v c 开发的工具应该还可以。最后,我想说的是,你需要亲自实践一下,自己去编写代码。可能有几个原因导致 BRBC 或 GRPC 的年代久远,完成时可能基于一些老旧的理念或特性。另一个原因是,Google 并不注重性能,只要能用就行,不关心这些细节,因此没有进行优化。

团队的后发优势在于我们能够专注于优化,将工作做到极致。关于杨安亭性能优化,我曾在之前的 POCP 社区大会上详细讲解过,感兴趣的话,你们可以回顾一下 QCP 大会的相关内容。实际上,团队已经实施了许多优化措施,例如实现零拷贝技术,从网络包的接收、反序列化到发送的整个过程都无需进行内存拷贝,这无疑是我们的一大优势。当然,这只是众多细节中的一点。至于 C++20,我目前正在进行相关工作,预计明年能够完成。这项工程相当庞大,我希望能够分享我在雅兰亭项目中的经验。我的初衷并非打广告,而是因为你们主动询问。

我将撰写一章专门讨论 RPC,深入剖析其本质,以及团队应如何打造一个完美的 RPC 系统。我将详细阐述我的技术选择、实现思路等,以便你们能够全面了解。我认为,了解 RPC 的优劣不应仅仅因为它来自谷歌就认为它一定优秀。你们需要明白其原理,比如它是基于 DSL 还是 pod buffer。之后,你们也可以将 Rust 的 RPC 与我们团队的 CORPC 进行对比,看看在实现相同逻辑时,谁的代码更加简洁。我对 CORPC 充满信心,因为它只需几行代码即可完成。例如,我已经在 GitHub 上分享了 CORPC 的一个示例。如果你们想看,我可以展示一下。

使用雅兰亭编写一个 API 服务器的第一步是编写业务函数,对吧?比如定义一个 API 函数,只需一行代码。接下来,创建服务器并设置监听端口,再注册 RPC 函数,最后启动服务器。总共只需四行代码。如果你们能找到一个只需两行代码就能完成的 RPC 框架,那我只能说你们比我更优秀。否则,就请接受这个事实。这就是易用性,我利用现代 C++的特性,将易用性做到了极致。只需几行代码就能完成任务。无论你们使用哪种语言,如 Go、Java、Python,Python 可能做到类似程度的简洁,但我相信不会比我们更简单。客户端的代码也同样简洁,只需几行代码即可完成创建连接和请求。服务端四行代码,客户端三行代码,总共一个服务器。接下来,我建议你们多实践一下,优先考虑使用雅兰亭。实践之后,我会发放资料。那么团队就先这样,谢谢大家。

相关文章
|
3月前
|
算法 C++ 容器
C++标准库(速查)总结
C++标准库(速查)总结
96 6
|
3月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
136 10
|
15天前
|
XML 网络协议 API
超级好用的C++实用库之服务包装类
通过本文对Boost.Asio、gRPC和Poco三个超级好用的C++服务包装类库的详细介绍,开发者可以根据自己的需求选择合适的库来简化开发工作,提高代码的效率和可维护性。每个库都有其独特的优势和适用场景,合理使用这些库可以极大地提升C++开发的生产力。
35 11
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
C++构建 GAN 模型:生成器与判别器平衡训练的关键秘籍
生成对抗网络(GAN)是AI领域的明星,尤其在C++中构建时,平衡生成器与判别器的训练尤为关键。本文探讨了GAN的基本架构、训练原理及平衡训练的重要性,提出了包括合理初始化、精心设计损失函数、动态调整学习率、引入正则化技术和监测训练过程在内的五大策略,旨在确保GAN模型在C++环境下的高效、稳定训练,以生成高质量的结果,推动AI技术的发展。
74 10
|
3月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
96 5
|
3月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
90 1
|
3月前
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
100 5
|
4月前
|
编译器 API C语言
超级好用的C++实用库之跨平台实用方法
超级好用的C++实用库之跨平台实用方法
57 6
|
10天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
51 18
|
10天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
37 13