天空 Blond - serialization
type: channel
link: https://skyblond.info/tag/serialization/
items: 【代码札记】从零开始的点对点聊天系统 P1

【代码札记】从零开始的点对点聊天系统 P1
type: item
date: 2022-11-29T18:52:31+08:00
description: 本文介绍这个点对点聊天系统的整体设计。其实在决定这一篇文章内容的时候犹豫了好久。周末看了Kotlin中文开发者大会，发现了协程这个东西，有点类似于Java即将实现的纤程——一种轻量化的线程，不需要操作系统来进行调度，因此能够节省线程切换引发的开销。看到了这种性能提升，我也想把他引入到这个系统中。但是这样的话又要等好久才能把代码定形，到时候指不定又看到了什么新东西，不断优化代码，最后文章怎么也写不出来。所以最后思来想去，决定先按照现在的样子写文章，以后再专门说优化。前言点对点系统是一种非常棒的框架。像是C/S和B/S这些传统框架中，总会有明确的主从关系。用户作为客户端，是被动的一方，需要向服务器，也就是主动的一方发起请求，然后获得主动方返回的结果。如果出于各种原因，客户端无法连接服务器，或者服务器崩了，那么整套系统也就不能用了。为了防止系统出现问题，领域内的先贤们搞出了诸如高可用、主从备份等各种各样的手段来尽量避免故障。而点对点系统则用另一种方式解决服务故障：如果把服务器分散给每一个用户，那么只要有用户使用，就有用户提供服务，而这个系统就永远也不会宕机。我想大家既然能点进来这篇文章，那应该都是用过BitTorrent的人。如果你对这个名字不熟悉，那你肯定知道「种子」和「迅雷」。BitTorrent就是一个点对点内容共享系统，系统中的每一个节点（参与者）既作为客户端向别人请求数据，又作为服务端向别人提供自己有的数据，从而实现了一种人人为我，我为人人的境界。但是点对点系统一般来说都比较难设计，即便设计出来了，使用体验上也不是很好。这就是为什么，现在能使用的大多数APP全部都是C/S结构的——要提升用户体验，只要在服务端投入即可。点对点系统要提升体验，就涉及到节点间互联质量，系统的公平机制，攻击防范等。节点间互联质量主要与网络有关，其中影响最大的就是防火墙。关于公平机制和攻击防范，主要是在设计上考虑如何防止系统被滥用，虽然比较复杂，但好在相对容易实现。本系列意图实现一个点对点的聊天系统，我想把主要的精力放在协议设计上，数据传输则委托给I2P处理。I2P也是由Java编写的，并提供了一个不错的API来供其他Java程序使用。此外为了防止I2P通讯部分的代码与MC部分的代码耦合在一起，我把这两部分的代码完全分开了。使用I2P处理通讯的部分放在了hurui200320/i2p-p2p-chat这个仓库，而与MC模组有关的代码则放到了hurui200320/MinecraftChatAlternative这个仓库里。关于I2P本身的内容我就不多说了，网络上有不少关于I2P的介绍。不过这里需要提及的一点是，I2P遵守的是比较纯粹的开源风格，也就是所谓的“As Is - No Warranty”。现在的主要开发者从创始人手中接下项目后，不接受任何来自社区的货币捐助，二十多年来完全靠他们对于隐私与自由的热爱在维护这个庞大的软件。我尝试着阅读过他的源代码，看到了2003年的Java，以及当时使用的构建工具。可以说在我的认知里，我还从来没有见过这样的项目。说回到“As Is - No Warranty”，作为开发者，他们会尽力让软件变得“能用”，但是他们并没有义务为你修复bug。所以如果你因为这个系列也喜欢上了I2P的功能，但在使用的过程中遇到了一些问题，我希望你可以自己动手做一些简单的调试工作，不要在语气上“指责”开发者（这一点经常无意识的发生，使得你在提出issue的时候听起来像是在指责你这个开发者在发布软件之前没有充分测试代码，从而给我带来了麻烦）。提供帮助，不要指责。协议设计关于这一套系统的协议，经过一番修修改改，最终是这个样子的。同步还是异步整个系统的消息采用异步的方式处理，从而实现复用同一条TCP链接。在早期设计中使用同步方式处理消息，这就意味着一个组件发出消息后必须占用这一条TCP直到收到回复，此时不能接收其他消息，也不能发送其他消息。这种情况下节点间的一条TCP连接就变成了半双工的方式，并且客户端和服务端需要轮流征用一条TCP，控制逻辑比较复杂。为了实现简单的全双工，可以让节点之间建立两条TCP连接，每条按单工方式使用。这样写起来非常简单：服务端只需要监听入站消息并处理，客户端使用单独的TCP连接发送出站消息。这种设计的好处是不需要太复杂的同步控制，但坏处也很明显，一对节点需要两条TCP，如果要和N个节点通讯，那就得2N条链接。如果其中一条链接中断了，我们便失去了发送或接收的能力，针对这种情况的错误处理又引入了额外的复杂性。总的来说，虽然异步设计会带来一定的复杂度，但整体框架建立起来之后的好处就非常可观了：单条链接实现全双工，可以提供类似Redis的单线程消息处理（和性能），整体消息设计也更加灵活。消息结构在消息本身的设计上，我采用了相…