什么是 Multiaddr

Multiaddr 最早的提出应该是 2014年6月2号在 Github 上这个 Issue: Binary packed network addresses 提出,后来在 mutiladdr 这里有了更加详细的描述。自称是可组合的面向未来的网络地址(Composable and future-proof network addresses)。

它将网络地址和各种协议组合,被描述为各种更加具体的协议和网络地址的组合:

  • /ip4/1.2.3.4/tcp/1234 -> 表示 ipv4 协议,地址 1.2.3.4,端口 1234,TCP 连接
  • /ip6/[::]/UDP/1235 -> 表示 ipv6 协议,地址 [::],端口 1234,UDP 连接
  • /ip4/1.2.3.4/tcp/1234/tls/p2p/QmFoo -> 表示 ipv4 协议,地址 1.2.3.4,端口 1234,TCP 连接,P2P 的 PeerId 为 QmFoo

还可以组合成更加复杂的形式:

  • /ip4/1.2.3.4/tcp/1234/p2p/QmR/p2p-circuit/p2p/QmA
  • /ip6/[::]/UDP/1235/quic/tls/ws

等等可以随意组合的形式。

它要干嘛

从上面的例子可以看出,Multiaddr 有着比传统网络地址更多的信息,更友好的描述,可以直接描述一段网络地址的 IP 和端口外,还有网络协议,加密协议等等的信息。 从左到右看过去就能明白这段网络的组成由什么协议、甚至是什么加密算法、安全套见组成。是想形成一种更加通用的,对人阅读更友好,以及对机器解释友好的描述。

嗯,对人阅读更友好,从左到右可以直接阅读它的组成,用斜杠分隔出的各个部分,可以让人通过其格式理解它们各自的含义。比如将 /ip4/1.2.3.4/tcp/1234 分隔后为

  • ip4 -> ipv4 协议
  • 1.2.3.4 -> 用点分隔的四组 255 以内数字,是 ipv4 协议的 IP 地址
  • tcp -> TCP 协议
  • 1234 -> 65535 以内的数字,端口号

在 Multiaddr 里 ip4 或者 ip6 的 IP 协议后面一定会跟着 IP 地址,Tcp 或者 Udp 后面一定会跟着端口号。类似的 DNS 后面一定会跟着域名:/dns4/www.example.com;P2p 后面一定会跟着 PeerId:/p2p/QmR。所以很容易看出协议和它绑定的上下文,比如 ip4,它的 IP 地址就在它下一位。

这对机器的可理解性也有帮助,电脑在解释 Multiaddr 的时候会从右往左解释,并将从右边读取到的信息作为左边剩余信息的上下文。例如:/dns4/example.com/tcp/1234/tls/ws/tls,从右往左读取的第一个信息是 tls,表示使用 TLS 传输加密,剩下的 /dns4/example.com/tcp/1234/tls/ws,右边第一个为 ws,表示使用 WebSocket 连接,然后继续解释剩下的 /dns4/example.com/tcp/1234/tls,右边的 tls(重复了对吧,先别管),表示 WebSocket 连接使用 TLS 传输层加密;接着是 /dns4/example.com/tcp/1234,读取到 1234,暂时不理解什么意思,会跟下一个 tcp 一起解释,解释为 TCP 连接,端口号为 1234;剩下的 /dns4/example.com 先读取到 example.com,无法解释,跟端口一样作为上下文给下一个 dns4,这下明白了,是作为 DNS 协议的一部分。

这种方式可以用来描述任意的网络协议,任何你想要组合的协议都可以往里面塞:/ip4/1.2.3.4/udp/12345/noise/quic/p2p/VM2T,有着极强的包容性,人类可读以及机器可读的形式。以及可以很容易地在机器中表示为二进制的形式。

到目前位置它的实现有:

看看就好

然而就我目前的体验来说,并不是说目前你有用到网络协议的写法都推荐为使用 Multiaddr 的方式。这种写法的可读性是非常好的,但是加大的程序实现的复杂性,有着各种语言提供的 multiaddr 库,但是使用 multiaddr 的程序,仍然需要自己解析出其中的协议。

程序工作量大

如果我想构建一个 Transport,里面封装了用于网络通讯的 ip协议、地址、端口等等信息,我使用了 multiaddr 作为构建的参数,定义了一个方法:

fn new_transport(multiaddr) -> Transport {}

这个对于 multiaddr 中协议的解析和构建的工作量是较大的。需要检查 multiaddr 里有 ip4 还是 ip6,获取到 IP 地址,再检查有 tcp 还是 udp,获取到 端口,才能在程序中构建出一个 Socket。如果有同时出现 ip4 和 ip6 的情况,要根据这类情况做出处理。对于 multiaddr 所支持的众多协议来说,处理的工作量是很大的。

程序耦合差

可读性友好但是对于程序不友好,实际开发过程中可能不如传统的方式。如果程序一开不知道自己要构造什么连接,希望作为参数传入,那么更好的做法应该是直接传入想要的参数,而不是传入一个 Multiaddr 后解析里面有什么。对于 /ip4/1.2.3.4/tcp/1234,这种写法,完全可以用

{
	'ipAddress': '1.2.3.4',
	'port': '1234',
	'protocol': 'tcp'
}

这种方式代替,因为 IP 地址格式的本身就说明了其要使用的 IP 协议是 v4 还是 v6,也可以少掉解析 Multiaddr 这一步骤。如果在加上其他相关的网络协议,tls 等,会增加构造 Transport 程序的复杂度。

总结

Multiaddr 是一种有有趣的地址方式,但是目前没有配套的成套工具,造轮子的成本也高,不推荐使用。