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 工具,了解如何运行它。
选择一个示例文件,例如 example\chat_room.cpp,然后点击上方的“Debug”按钮进行编译。由于系统可能正在处理先前的编译任务,可能会出现短暂的卡顿,请稍候。若编译成功,您将看到相应的成功提示。尽管当前温度尚未达到SVC的标准,但编译过程仍在顺利推进。
编译完成后,我们即可开始运行程序。需要注意的是,编译速度确实较为缓慢,这可能会带来一些等待的焦虑。编译过程仍在进行中,速度相对较慢,但请保持耐心。一旦编译成功完成,我们就可以继续进行下一步操作。值得一提的是,这台电脑的主频仅为2.2GHz,且为双核配置,属于一台相对老旧的设备。
编译完成后,我将简要介绍代码的功能,随后团队即可着手开展一些有趣的工作。
举例来说,我之前构思了一个聊天室的创意,旨在让所有人都能参与进来,共同交流,并计划将其实现为一个简单的网页应用。
实际上,实现这一目标所需的代码量非常少。在动手之前,我先为大家演示如何搭建一个文件服务器。假设你有一个大文件需要分享,使用雅兰亭可以轻松实现,几乎无需编写代码。现在,让我们回到代码编译的话题上。请大家保持编译进程继续运行。以Chat Room为例,第一行代码仅用于打印当前的工作目录,由于环境差异,你们本地可能不存在这个路径。你们可以选择稍后添加代码,或者现在就着手添加。
添加完成后,运行代码,查看执行进程所在的当前目录,因为这个目录对团队后续的工作至关重要。如果团队打算搭建文件服务器,需要将文件放置在执行目录中,以便实现下载功能。因此,请添加一行代码来打印当前目录,这样你们就能清晰地看到当前的工作目录。使用fs.currentPath来获取并显示这个路径。
现在开始编译C++程序,这确实需要一些时间。编译成功后,运行程序即可查看目录。目录查看完毕后,接下来团队将讨论如何编写文件服务器。首先,团队需要指定服务器的线程数。
第一个参数即为线程数,代表你希望服务器使用多少个线程来提供服务。我这里默认设置为一个,但你可以根据CPU的核心数进行调整,比如八核或三十二核,只需输入相应的数字即可。
第二个参数是服务器监听的端口,这里我们设置为9001端口。后续的下载操作都将通过这个端口进行,这是最基本的配置,一行代码即可完成设置。
有了代码基础后,接下来的第二、第三行代码至关重要。这两行代码用于设置静态资源的目录,即指定文件存放的位置。如果这两个参数为空,则默认将文件放置在执行目录中。团队已经查看了第一行代码输出的目录,接下来,团队需要将静态文件复制到该目录中,然后启动服务器即可。
下面进行演示。假设你已经设置好了一个文件服务器,你只需设置静态资源的目录并启动它。如果你希望更加灵活,可以将这两个路径参数从main函数的参数中传入。因此,理论上,你可以通过配置这两个路径,实现零代码的文件服务器。这确实是一个值得探讨的问题。让我详细解释一下。
假设你需要编写一个文件服务器程序,首先,你必须指定一个基础的物理磁盘路径,这通常被称为基础目录或base目录,作为存放静态资源的根目录。然而,团队可能还有额外的需求,比如希望添加一个虚拟目录。
第一个参数就是虚拟目录的名称。这样做的原因是,直接暴露物理磁盘路径可能会面临基于目录的攻击风险。因此,通过设置虚拟目录,可以有效地隐藏实际的物理路径。虚拟目录与物理目录是相互映射的。
第一个参数填写虚拟目录的名称,第二个参数填写实际资源所在的物理路径。为了简化说明,这里我直接使用当前目录作为示例。启动服务器后,团队成员可以查看资源。
例如,如果我在当前目录下的一个名为Example的文件夹中,那么我将展示Example文件夹中的资源。大家也可以在自己的Example文件夹下查看,比如在Excel目录下,你会发现一些有趣的资源。我建议使用BatchGIGBH命令来查看,这样会更方便。
给大家展示一下,我的当前执行文件目录中包含了许多文件,这些文件都是静态资源。例如,这里有一个MP4视频文件和一个HTML静态页面。
稍后,我将向大家展示如何下载MP4视频文件。理论上,只需输入团队的IP地址、端口和文件名,即可完成下载。此外,还有一个方法,我很好奇你们是否已经尝试过使用CR工具。请回复一下,确认一下。现在,我们的目标是下载之前名为testmp4和test2mp4的文件。首先,我们需要删除之前的文件(如果存在)。接下来,请跟随我的步骤操作。首先,输入应用地址。地址填写完毕后,我们希望下载的是通过HTTP 9001端口提供的test.mp4文件。填好命令后,执行它。
大家可以看到,文件已经下载完成。这样,我们无需编写任何代码,就能实现文件服务器的功能。你们也可以尝试一下,只需使用Curl命令,输入服务端静态资源的地址即可。检查一下文件是否下载完成,如果完成了,我们再下载另一个文件,即test2.mp4。然后,团队可以验证一下下载的文件是否与服务器上的文件一致。如果能正常播放,那就说明文件是完好无损的,与服务器上的文件完全相符。你们中如果有已经下载完成的文件,可以尝试运行一下,看看是否能正常播放。
接下来,团队需要了解的是,我们不仅可以使用工具下载文件,还可以通过浏览器进行下载。请在浏览器中输入相同的网址,就像之前一样。看,我已经下载完成了,你们也完成了吗?如果还没有,那可能是因为它遵循了标准的HTTP协议。
无论是通过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目录,或者是Example Output目录。你们可以在里面随意选择一个文件,将文件名输入到浏览器地址栏中(在9001端口后面加上文件名),然后进行下载。下载完成后,就说明静态文件服务器已经配置完成了。
在完成视频下载功能后,团队接下来要面临的问题是:如果我拥有大量视频资源,该如何建立一个视频网站?毕竟,下载文件只是基础功能之一。接下来,团队将开始着手打造视频网站。实际上,一个HTML页面就足以实现视频网站的基本功能。我将打开一个本地HTML页面供你们参考。
由于你们本地可能没有视频文件进行测试,我在思考如何将视频文件分享给你们。或许,你们可以通过访问我的IP地址来获取视频文件。但是请注意,我们目前处于同一个局域网中。如果你们不在同一个局域网内,那么这个方法可能就不适用了。不过,我有一个解决方案:我可以将文件上传到我的云端服务器上。
这样,你们就可以通过访问云端服务器的IP地址来下载视频文件了。我的云端服务器IP地址是47.96.38.82:9001,你们可以尝试访问并下载test.mp4文件。同时,你们也可以使用Curl命令来检查这个地址的访问情况。
由于我设置了限速功能,所以默认下载速度可能只有100KB/s左右。当然,这个速度是可以根据需要进行调整的,可以在服务器设置中进行相应的配置。接下来,我将展示如何设置每次下载的大小限制以及速度限制等参数。
稍等一下,我将调整速度限制以便更快地传输文件。我将Transfer的Size设置为4MB,这意味着每次可以传输4MB的文件数据(当然这也受限于网络速度和时间等因素)。由于实际带宽情况可能有所不同,我还需要调整另一个参数来优化下载性能。
由于下载的人数可能较多,我将线程数设置为10个。这样即使我的机器是双核的也能尽可能地并行处理多个连接请求并充分利用CPU资源。
请大家参考我的配置进行修改:首先将线程数设置为类似我自己的配置(如果可能的话同时拥有十个并发连接);然后根据实际情况调整其他相关参数以确保服务器能够稳定运行并满足下载需求。
目前你们可以看到已经有多个连接在请求下载了我的日志也显示了“new connection coming in”等信息;现在网速大约是100K左右(有90多K的下载速度);因为服务器是双核的总带宽大约是三兆左右;我估计几分钟后显示的总带宽将会达到十几兆左右。
现在你们看到下载量了吗?AHHTTP显示的IP地址是我云端服务器的地址(47.96.38.82);你们可以看看自己下载了多少数据。
如果我要做一个视频网站但是没有视频内容会有些遗憾;不过没关系你们也可以看看我做的内容或者自己录制一些视频来上传测试。
最好的办法是直接在本地找一个MP4文件进行测试;如果方便的话就不用去下载公网上的资源了。你们可以使用Windows自带的录制工具(比如使用“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文件。
请确保你的目录服务已经启动。如果在当前目录下找不到文件,请尝试检查其他位置,例如IP地址47.96.38.82上的Index.Html文件是否存在。如果仍找不到,我将检查我的文件并确保它们被正确放置。请稍候片刻,然后再次尝试下载。
如果服务尚未启动,请稍等,我将进行确认。
看来我之前的文件放置位置有误,现在已更正。请再次尝试下载。如果你已经下载了HTML文件,可以通过网页或QQ等方式进行查看。在浏览器中输入IP地址47.96.38.82,然后尝试下载Index.Html文件。请检查所有设置是否正确。
现在,你可以在浏览器中输入该IP地址,并重启你的服务。一旦文件放置正确,重启后你应该能看到你的视频网站。如果你已经下载了所需文件,请确保将它们复制到当前路径下,包括你录制的视频文件。完成这些步骤后,你的视频网站应该能够正常工作。
请确保服务已经重启,并且index.html页面已经复制到正确的位置。如果你需要使用curl命令下载文件,那是完全可以的。
没有HTML文件,这些内容也无法通过其他方式实现。那么,你现在能在本地观看《三国演义》或其他视频吗?首先,请确保index.html以及相关的ATS、N、P4文件都在你的当前执行文件目录下。然后,尝试刷新页面。如果仍然无法正常显示,可能是文件本身存在问题。请检查你的MP4文件名是否为test.mp4,因为我在页面上已经将其写死为test.mp4。如果没有这个文件,当然会显示为灰色。
如果你录制的视频文件不见了,或者输出的是目录而不是文件,请尝试重启服务。因为它是静态文件服务器,所以刷新页面或重启服务后应该能看到变化。如果你在本地使用127.0.0.1访问时遇到问题(不确定是否是Logo显示问题),请查看Output中的资源目录,并参考Output Example中的Test.Mp4文件。通常应该没有问题。
直接执行Index文件,并确保所有相关文件都已正确复制。如果执行文件没有运行,请先关闭它。可能是之前的服务没有停止,或者存在重复执行的情况。请检查并确保只运行了一个服务实例。
如果仍然无法访问test.mp4文件,那么至少有一半的同学已经完成了设置。接下来,团队将讲解ETM页面以及网站是如何播放视频的。其实原理很简单,大家可以看看HM页面,它的构造非常简洁,主要基于标准的HTML标签,特别是HTML5中的Video标签。在Video标签中,我们设置了视频的长宽尺寸以及资源地址。当你在浏览器中输入网址并附加Test.Mp4时,它会自动访问你的静态服务器并播放视频。整个代码非常简洁,只有几行HTML。
例如,我运行后刷新页面并点击播放按钮,视频就会开始播放。关于操作方式,基本上就是编写代码来实现各种功能。如果我想实现画中画播放或暂停功能等,原理都是相似的。
这样一来,一个视频播放网站就几乎完成了。你基本上只需要编写一个静态页面和一个脚本就可以实现大部分功能。团队通过雅兰亭编写的静态文件服务器几乎可以实现零代码操作这一点大家都能感受到。你可以通过网页或二维码进行下载或直接在浏览器中查看。
接下来团队要做的第二件事是创建一个聊天室。那么聊天室是如何实现的呢?首先团队会通过WebSocket技术来实现聊天功能。具体来说当你登录聊天室时需要输入一个用户名也就是你的昵称。输入昵称后聊天室会显示所有在线人员的列表。然后你就可以开始发送消息了。每当有人发送消息时系统就会向其他所有人广播这条消息以确保所有人都能看到。
接下来团队将展示部署在云端的聊天室大家可以进入体验一下。请注意代码中的单引号需要删除之前没删除的也不用管因为我之前故意加上的。现在你可以编译并运行代码了。
实际上,代码已经就绪,在GitHub上大家可以预览效果。“选错了”,这不是我们要展示的HTML文件,而是本地的服务器地址。我应该选一个远程的地址,看起来远程页面已经在服务器上配置好了。这样,大家只需输入网址就能访问。这里随便选个名字作为示例,如果默认,系统也会自动生成一个。这就是一个简单的聊天室应用。大家可以尝试访问(示例IP地址和端口号:47.96.38.82:9001/client.html,注意:实际访问时请使用提供的正确地址),如果没有特别指定,就是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”,即登出时需要更新用户列表。这两种逻辑基本上是相似的,因此可以将其纳入一个分支中进行处理。另外,还有一个消息类型需要说明,即当用户发送消息时,我们将其类型命名为“UserMessage”。
首先,我们从最简单的用户消息逻辑开始讲起。当你的前端发送一个消息时,通讯格式是一个JSON字符串。
这个JSON字符串包含两个字段:Type和Content。Type字段表示消息类型,这是由你自定义的。我这边定义的是,当用户发送消息时,Type为“UserMessage”,而内容则由两个字段组成,我将其定义为一个结构体,称为Message。这个结构体也有两个字段:Type和Content。例如,如果你发送了消息"AABBB",那么它就是一个简单的JSON字符串。
当服务端接收到前端发送的请求后,我的第一步是进行反序列化。因为这是一个JSON字符串,所以我需要将其反序列化到我的对象中。由于通讯协议是我确定的,它包含两个字段:Type和Content。这一步,我将其反序列化到结构体中。解析完成后,我接下来判断消息是否为“UserMessage”类型。
如果是“UserMessage”类型,意味着消息是由用户发送的,需要进行处理。首先,我需要获取当前所有客户端的列表,因为我的聊天室可能有十个人,也可能有一百个人。因此,我的代码逻辑是使用一个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上也可以运行。部署到云端的方法也是同样的因为我们的团队开发的程序是跨平台的你可以随意选择任何平台进行操作。
分享一些经验:实际上当你需要开发服务端时只要合理利用库C++的开发效率同样可以很高。我的代码就是基于一个菲律宾开发者在GitHub上的项目其服务端实现仅包含300多行代码核心代码甚至不到100行包括头文件在内也只有132行。因此C++的开发效率确实可以很高。我想传达的理念是:许多人认为C++难以掌握但实际上问题往往出在方法不对上。对于编写库的人来说、对于使用者来说都是如此。西亚最大的问题就是缺乏好用的技术。
这就是我正在做的事情我称之为兰亭集库就像兰亭集序中的诗一样美丽。你可以随意使用如果库中没有你需要的功能你可以向我提出需求我会帮你实现。团队的GitHub上经常有人提出新功能的需求比如最近有人提出希望KET能支持扩展功能我说可以但需要给我一些时间来实现。
03.业务优化
另外,我还要介绍一点,现代C++能够让团队的逻辑代码变得异常简洁。首先,在网络方面,我采用了协程(Coroutine),这是C++20中引入的功能。你们可以深入了解协程的实现,我稍后会向你们展示。协程的使用,不仅让代码更加简洁,还显著提升了效率。协程类似于Go语言或Python中的Await关键字,其工作机制是这样的:重要的一点,服务端之所以高性能,是因为它采用了纯异步实现,不会阻塞当前线程。
得益于协程,许多服务端,包括Node.js,或是其他同步单线程服务端,在效率上都显得相对较低。当使用协程等待WebSocket消息时,线程不会被阻塞。协程会暂停当前函数,将控制权交还给调用者,调用者可以继续执行其他任务,而无需等待当前线程。因此,这是一个完全异步的过程。当未来某个时刻收到消息,比如登录消息,协程就会恢复执行,并继续处理后续逻辑,如广播阶段,完成后再次返回。整个过程没有任何阻塞。
这就是C++协程的一大优势:允许我们以同步的方式编写异步代码。想象一下,如果团队需要使用传统方式编写回调函数,那将是多么繁琐。然而,在C++20中,我们迎来了一项革新:无需再编写回调函数。以同步逻辑编写代码不仅易于理解,编写起来也更加轻松。
因此,C++20赋予了我们强大的能力——这不仅仅是语法上的改进,而是一个真正的语法增强。尽管协程的概念历史悠久,早在70年代就已存在,但直到2020年才被正式纳入C++标准。在此之前,尽管也有协程的实现,但它们并未标准化。
如今,协程已成为标准化的一部分。基于C++协程,团队封装了一个名为think simple的协程库,它已被集成到团队的CORE TP server中。因此,现在你可以非常轻松地编写代码,从头到尾按照同步逻辑进行即可。在需要调用异步操作时,只需添加一个co_await即可。所以,我希望团队的雅兰亭库能够帮助你们快速构建高性能应用。
今天的工作坊应该已经达成了两个目标:一是视频网站,我已经看到你们上传的视频了,虽然未能分享给大家,有些遗憾,但第二个目标——团队的聊天室,大家都展示了各自的才艺,我也在里面看到了,这真是一件令人开心的事情。
所以,团队工作坊的目标已经达成:使用一个好的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.4.21.07版本,仅仅是升级标准就耗费了如此长的时间,因为需要修复大量的测试用例,可能以前存在的一些未定义行为或不规范的写法,在新标准下都暴露出了问题。在修复这些问题的过程中,你需要运行持续集成(CI)和压力测试,这个过程是相当漫长的。团队追求的是稳定性,所以升级步伐不会太快。最理想的情况是,新项目从一开始就直接采用新标准,没有历史遗留问题的束缚。
无论是开发应用程序、远程过程调用(RPC),还是网络服务,都很简单。你只需启动团队的代码服务器,编写一个简单的echo服务,返回"hello world",并确保线程数与请求数相匹配。然后,利用名为WRK的压测工具来测试你的服务器性能。将你的服务器与团队的服务器进行对比,观察哪一个性能更优,同时监测CPU的使用情况。
例如,我曾经对旧服务器进行压测,CPU使用率高达60%,而每秒查询数(QPS)大约是10万;而团队的服务器CPU使用率仅为30%,QPS却能达到50万。显然,你会选择性能更优的后者。而且,我的代码量只有原来的五分之一,我对此非常有信心。如果你之前使用的是BRPC或GRPC,那么性能差异可能会更加明显。我曾经做过一次测试,GRPC的QPS非常低,可能不到10万,而团队的RPC服务器在96核的服务器上测试时,QPS可以达到400万,大约是前者的十倍。所以,你可以使用第三方压测工具,在相同的环境下进行测试,并通过结果来进行对比。当然,你也可以顺便检查一下你的原始业务逻辑代码行数,以及现在需要编写多少行代码,这是一个非常直观的比较过程。
在我看来,是否升级实际上取决于你们领导是否足够重视性能。但更重要的是,如果在升级过程中遇到问题,他是否真正关心。比如,当你发现性能瓶颈或痛点时,团队通常会主动介入。假设你告诉我,你的代码简洁、性能良好,并发数高,那么你可能不需要进行更换。但如果你某一天意识到性能需要优化,你就会迫切地要求更换,甚至会不断催促团队进行升级。因此,你必须在遇到性能瓶颈时及时提出,这样你可能很快就能完成升级。团队的另一个经验是,如果没有遇到痛点,你就很难推动升级。
举个例子,团队有一个训练化库叫做STARTPACK。曾经有一个业务方在UC浏览器的广告索引中使用了它。他们之前使用的是protobuf,将数据存储到数据库中进行索引。当日页面浏览量(PV)达到300亿时,大家可能认为从文件中检索索引非常快,但在达到300亿日PV时,它就成为了性能瓶颈。这时,团队提出了两个解决方案:首先,团队的性能比protobuf高出四五倍;其次,他们希望在提升性能的同时压缩二进制文件大小。
原来使用protobuf时,内存占用大约为16-17GB,而使用团队的FatPack后,内存降至大约7-8GB,性能也得到了提升。他们对此非常满意。因此,你必须发现性能瓶颈才能推动团队进行升级。这是团队的经验。如果你主动向他人推荐新方案,他们可能会说:“我有我自己的方案,万一引入Bug或系统崩溃,谁来负责?”而且,很多人会以性能或网络问题为由拒绝改变。所以,如果没有遇到问题,不要去强行推动升级。当遇到痛点时,再深入挖掘并推动改变。这是团队的经验,甚至他们会主动找到你去推动升级。
关于aSoul,它实际上是Boost的一部分,但它有独立的仓库。作者先有了aSoul,然后将其移植到Boost中。因此,团队依赖的是独立版的aSoul。这里我想补充一点,aSoul本身并不大,可能只有几兆字节。它最大的优点是跨平台,封装了Linux、Mac和Windows的接口,提供了统一的接口。如果你想编写跨平台程序,aSoul是一个很好的选择。很多时候,人们认为自己编写的代码性能会更好,但这并不总是正确的。
你可以自己编写一个import的应用服务器,并与基于aSoul的应用服务器进行基准测试。如果你有信心打败它,那么你很厉害;如果你没有打败它,那么为什么不使用现成的呢?关键是它是一个经过多年验证的工业级库,你可以放心使用。而且,它还能节省你很多时间。
在迁移方面,团队开发了一个名为“倚天”的迁移工具。这个工具的初衷是将许多老业务从X86架构迁移到ARM架构上。X86架构与ARM架构存在不同,例如,在C++中,默认的内存序在X86上是强内存序,在ARM上是弱内存序。如果你之前在X86上将代码当作强内存序来编写,那么移植到ARM上时,肯定会出现线程安全问题。
团队推出的“倚天”工具会检查所有与内存序相关的代码,提示你注意潜在问题。但除此之外的大多数场景,只要你使用标准C++编写代码,你的行为是确定的,因为它符合标准。例如,当你调用一个函数并期望返回文件路径时,它会返回文件路径或错误,不会因为你是X86还是ARM而有所不同。还有,如果你之前调用的是系统API,那么一般没有问题,只需调用标准的C++库即可。实际上,迁移过程中遇到的问题并不大。
关于静态分析,有很多工具可以使用。例如,团队有一个名为aSon的工具,它非常有用。aSon可以帮助你检测内存泄漏和溢出。团队通常会开启aSon,以便及时发现并检查内存泄漏问题。例如,如果你在主函数中使用while循环,并且在连接关闭时没有跳出循环,那么就会发生内存泄漏。因此,团队要确保协程不会泄漏内存。
有时候,团队在编写代码逻辑时会忘记这一点,这时开启aSon就会提醒你哪里发生了泄漏,需要检查。这就是aSon的基本工作原理。基本上,aSon可以满足大多数需求。理论上,团队的代码没有内存泄漏问题,也没有越界问题。对于Windows平台,我不太熟悉,但听说微软开发的工具应该还不错。不过,这需要你亲自实践一下,自己去编写代码进行验证。
可能有几个原因导致BRPC或GRPC性能不佳:一方面,它们可能完成于一些老旧的理念或特性之上;另一方面,Google可能并不特别注重性能优化,只要能用就行,不太关心这些细节问题。而团队的后发优势在于我们能够专注于性能优化,将工作做到极致。
关于杨安亭性能优化的话题,我曾在之前的POCP社区大会上详细讲解过。如果你们感兴趣的话,可以回顾一下POCP大会的相关内容。实际上,团队已经实施了许多优化措施,例如实现零拷贝技术。从网络包的接收、反序列化到发送的整个过程都无需进行内存拷贝,这无疑是我们的一大技术优势。当然,这只是众多细节中的一点。
至于C++20的相关工作,我目前正在进行中,预计明年能够完成。这是一项相当庞大的工程。我希望能够分享我在雅兰亭项目中的经验。我的初衷并非打广告,而是因为你们主动询问。我将撰写一章专门讨论RPC的内容,深入剖析其本质以及团队应如何打造一个完美的RPC系统。我将详细阐述我的技术选择、实现思路等以便你们能够全面了解RPC的优劣。我认为,了解RPC的优劣不应仅仅因为它来自谷歌就认为它一定优秀。你们需要明白其工作原理和底层实现细节,比如它是基于DSL还是pod buffer等。之后,你们也可以将Rust的RPC与我们团队的CORPC进行对比看看在实现相同逻辑时谁的代码更加简洁。
我对CORPC充满信心因为它只需几行代码即可完成相关功能实现。例如,我已经在GitHub上分享了CORPC的一个示例代码。
使用雅兰亭编写一个API服务器的过程非常简单:首先是编写业务函数比如定义一个API函数可能只需一行代码;接着创建服务器并设置监听端口;然后注册RPC函数;最后启动服务器即可。整个过程总共只需四行代码。如果你们能找到一个只需两行代码就能完成的RPC框架那我只能说你们比我更优秀。
否则就请接受这个事实:这就是易用性!我利用现代C++的特性将易用性做到了极致。无论你们使用哪种语言如Go、Java或Python(尽管Python可能做到类似程度的简洁性但我相信不会比我们更简单),客户端的代码也同样简洁只需几行代码即可完成创建连接和发送请求的操作。服务端四行代码客户端三行代码就能构成一个完整的服务器应用。接下来我建议你们多进行实践并优先考虑使用雅兰亭进行开发。实践之后我会发放相关资料给你们。谢谢大家!