藏川线前段

--- 摄于 2017 年 9 月 藏川线前段

wasm 相关的内容,基本已经形成了 PR 或者已经合入了主支了,这一篇主要说的是 tentacle 最近的相关修改,这东西也是有点意思的,属于之前没做过,第一次尝试去做,内容属于知道的觉得很简单,不知道的觉得很神奇——就是同端口支持多协议到底是怎么回事?

网络协议到底是什么

这个问题其实可以扩大化,所谓协议到底是什么?很简单,它其实是一套行为规范,具体到内容,简单地说,就是一个消息怎么解析、打包、处理、响应。每一种协议都是一套消息处理的行为规范,而基于某个协议的上层协议实际上就是在前一种协议的基础上,加上自己的一套逻辑,进行抽象和嵌套,最后形成了自己的一套消息行为规范。

常见的网络协议,包括最底层的 TCP/UDP,以及基于它们的比如 TLS/HTTP/Websocket/KCP/QUIC,在某种程度上来说,后者其实底层都是前者,都是基于前者做的客制化而已。那么从理论上来说,在 TCP 层面上,都可以通过熟悉/解析他们的数据包,来识别正在通信的是什么协议。这其实就是同端口支持多协议核心的理论点,比较常见的场景就是代理,比如 Nginx 协议分发场景。

tentacle 的 TCP Upgrade

这次介绍的东西,实际上就是基于这个 PR,看上去代码很多,实际上只实现了一个功能,将基于 TCP 的所有协议都作为 Upgrade 对象,支持在同一个端口进行识别和建立连接通信。之前版本的 tentacle,支持 tcp/tls/ws 三种协议,但是,他们必须监听在不同的端口,支持的协议越多,占用的端口就越多,这样理论上问题不大,实际上对于同一个应用来说,多端口实现多协议会对运营和维护造成一定的障碍:

那么这个东西的实现,其实我在这篇文章中讲解不是很多,只挑部分重点核心代码,其余的并不十分重要,开始贴代码,原代码在这里,文章中有删减:

async fn protocol_select(stream: TcpStream, mut upgrade_mode: UpgradeModeEnum) {
	let mut peek_buf = [0u8; 16];
    stream.peek(&mut peek_buf).await?;
	loop {
        match upgrade_mode {
            UpgradeModeEnum::OnlyTcp => {
                进行 tcp 的下一步操作
                break
            },
            UpgradeModeEnum::OnlyWs => {
                进行 ws 的下一步操作
                break
            },
            UpgradeModeEnum::OnlyTls => {
                进行 tls 的下一步操作
                break
            },
            UpgradeModeEnum::TcpAndTls => {
				if peek_buf 是 tcp 消息 {
                    upgrade_mode = UpgradeModeEnum::OnlyTcp;
                    continue;
                } else {
                    upgrade_mode = UpgradeModeEnum::OnlyTls;
                    continue;
                }
            },
            UpgradeModeEnum::TcpAndWs => {
                if peek_buf 是 http 消息 {
                    upgrade_mode = UpgradeModeEnum::OnlyWs;
                    continue;
                } else {
                    upgrade_mode = UpgradeModeEnum::OnlyTcp;
                    continue;
                }
            },
            UpgradeModeEnum::All => {
                if peek_buf 是 http 消息 {
                    upgrade_mode = UpgradeModeEnum::OnlyWs;
                    continue;
                } else {
                    upgrade_mode = UpgradeModeEnum::TcpAndTls;
                    continue;
                }
            }
        }
    }
}

这段代码的核心就是:

这里面 loop 其实就是 goto,为了代码复用和好看,不然每个分支除了判断之外,都是重复的内容,其实有的时候,goto 语法是有很好的作用的,可惜 rust 基本不太可能支持了。不过 loop + break ‘label 也算是勉强能模拟 goto 了。

那么为什么是 16 bytes?很简单,拍脑袋,如果用正常的思路,它是需要集合所有支持的协议的第一个消息的内容,找到最小的消息,然后读这部分消息,判断是否是这个协议,然后以此类推,把所有协议都识别出来。这样是最正统的思路,但是,它的开销(包括识别协议的开销和程序员的开销)是相对比较大的:

最后

关于 lightclient 的进展,已经形成了 PR,期待它通过各种稳定性和功能性测试,正式合入主支,这样就就可以看看后续存在什么样的影响了,这是我比较期待的地方。

评论区

加载更多

登录后评论