GSoC 2022 Series - 2

系列导航

熟悉代码

联系 mentor 后他让我去看 src/rpc 下的代码,JSON-RPC 协议以及 SCGI 协议的实现都在这里,原先只有 XML-RPC,mentor 为它增加了 JSON-RPC 的支持:根据 XML-RPC 中的接口定义了一个 IRPC 接口类,然后让原先的 XML RPC 以及新增的 JSON RPC 继承自这个接口,通过 RpcManager 将 RPC 请求根据其内容是 XML 还是 JSON 分派到对应的处理类中然后再返回结果。一开始看代码还是比较混的,画一下分析图对于理清类之间的关系和工作流程会很有帮助:

此时的 RPC 的网络通信部分是由 SCGI 协议实现的,而这个项目的目标之一是将古老的 SCGI 协议替换为更加 Modern 的 Websocket 协议。当时我的想法是照猫画虎地增加 websockets.h、 websockets.cc 这样两个文件去实现 Websocket 协议。彼时的我还不知道 SCGI 协议是基于 rTorrent 的核心依赖 libtorrent 中的 event loop 实现的,也不清楚代码中 ScgiSCgiTask 这两个类的关系,后来花了更多时间去阅读代码,结合 libtorrent 中 event loop 部分的代码理解之后才理清楚:ScgiSCgiTask 的关系类似于 reactor 模型中主线程和子线程的关系,Scgi 是 listener,activate 之后把自己放在 event loop 中,event_read 中将每一个可读事件封装为 SCgiTask 然后将其放入 event loop 中。

uWebsockets 和 uSockets

uWebsockets 是一个 C++ 纯头文件实现的 websocket 库,性能优越,API 很简洁优雅,github 上有 13 k starts,计划用这个库来引入 websocket,不过在编译安装这个库的过程中还是遇到了很多麻烦的。uWebsockets 能做到纯头文件实现是因为底层用于 uSockets 作为网络库,在实验室的 GPU 上编译 uWebSockets 时报错 lto1: fatal error: bytecode stream in file ‘uSockets/gcd.o’ generated with GCC compiler older than 10.0 compilation terminated。 想着是链接器版本太低于是安装新的链接器,结果编译安装新版 binutils-2.38;成功安装了新版链接器后还是不行;转而安装版本低于 10.0 的 gcc … 装了 9.2 版本的还是不行。。。。。。吃完饭后在 Fisher 的虚拟机那里试了一下,一下子就编译过了。。。回来装了个 20.04 的 ubutun 虚拟机然后果然编译成功了,接下来就完全在虚拟机中开发了。这个过程记录在了前面的这篇博客中。

弄完这些后我还用 uWebsockets 写了一个小 demo 然后给 mentor 看,现在看来这个 demo 真是有够简单的。。。mentor 指出了这个库不支持 unix domain socket,但是这项特性对于 rTorrent 来说很重要:

The feature is essential because rTorrent RPC interface has arbitrary command execution capabilities but doesn’t have access control of its own. Unix socket could be access restricted as a file, much straightforward than a port.

unix domain socket

SCGI 的可以跑在 TCP/IP 的端口上,也可以跑在 unix domain socket 上,当时我还不知道什么是 unix domain socket,一番 Google 下来后才算有所了解。mentor 说跑在 unix domain socket 比较安全,希望 websocket 也能跑在 unix domain socket 上,但是 uWebsockets 不支持 unix domain socket,此时只有两个选择:一是自己为 uWebsockets 增加 unix domain socket 支持,而是另寻它库,当时觉得自己增加 unix domain socket 支持肯定很难就去寻找其他库。试了 libwebsocket,有 unix domain socket 支持,不过是 C 写的,mentor 说接口 old-style,确实很难看懂。他建议我去实现 uWebsockets 的 unix domain socket 支持,查了一些资料,感觉也还是没啥思路。转而去寻找是否有其他支持 Unix domain socket 的 Websockets 实现,看了 websocketpp 和 boost 的 websocket 好像都没有。

结果最后只能硬着头皮尝试在 uWebsockets 上实现这项特性,首先在 uWebsockets 和 uSockets 中翻遍了所有跟 unix domain socket 有关的 issue,确实有那么几个,其中对我有启发的是这个,让我知道了从 uSockets 的 bsd.c 这个文件中的创建 socket 部分入手,接着还在 uWebsockets 中提了一个 discussion,@了好多人都没人理我。。。实际动手操作后竟然真的让我做出来了,比想象中的简单许多,这也得益于 uWebsockets 本身的设计和编码都很优秀。在 uSockets 中提了 PR,想着这要是能合进去了那可就太棒了,不过作者认为这还不够,需要考虑兼容其他协议(其实作者的意思我也不太理解),这 PR 就先放着吧,日后有时间再 work on it

qualification task

mentor 后来发布了 qualification task:

Implement a minimal WebSockets -> SCGI “translator” program in C++. Note that you don’t have to implement it inside rTorrent, and you don’t have to worry about the complicated stuff like events. Here is a simple JSON-RPC client for rTorrent. Currently it accepts command line arguments. Your task, basically, is to let it accept arguments from WebSockets instead. Alternatively, just passthrough the payload and pipe back the response.The experience you gained here will also help with the main project.

感觉不太难,clone 了 mentor 给的 simple JSON-RPC client 想跑起来的时候遇到了问题:在尝试用 ip:port 连接 rtorrent 的时候总是报错 Failed to connect: -1,用 unix domain socket 连接的时候就正常,搞了很久还不知道是什么原因。。。后来在详细看代码才发现是 mentor 在 sin->sin_port = ::htons(std::stoi(port)) 这里竟然写错了变量名。。。。。。slack 上联系他说了这个事情,问他是不是自己都没用 ip:port 跑过,果然如此。。。还在想着会不会也是 qualification task 的内容之一。

这个 task 我是很认真完成了的,放在了代码都放在了我 github 上,在这里可以看到。对于内部数据流转的这个设计甚至自我感觉良好哈哈哈哈哈哈哈

more

4 月 15 号的时候联系 mentor 说我为 uWebSockets 增加了 unix domain socket 的支持,并且计划向官方 repo 提 PR,不过 mentor 也说他 open to a custom fork so that shouldn’t be a big problem,这样的话就算 PR 不被 merge 的话也可以用我自己的 fork。5 月上旬提交了两个 commit,在 rTorrent 中引入了 websocket,支持 TCP/IP 端口以及 unix domain socket 的连接,跟 mentor 说了这事儿,得到反馈还不错,至此感觉应该没啥大问题了哈哈哈哈


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!