[BT-0x00] 聊聊Bencode的Byte String
Bencode作为一种编码二进制数据的方式,是BitTorrent协议中重要的组成部分之一。 它既是.torrent文件的编码格式,也是BitTorrent协议中的消息编码格式,还是DHT协议中的消息编码格式。 本篇博客虽然不会讲解Bencode的具体实现,但是为了聊聊Bencode中的一些不足之处(Byte String),我首先还是要先介绍一下Bencode的编码规则。 具体的官方文档可以参考 这里。 Bencode的编码规则其实相当简单,只有四种基本类型: Integer 就是整数,可以为负数,以i开头,以e结尾,中间是整数的十进制表示,比如i123e表示整数123,又比如i-123e表示整数-123。当为0时,不可以有-号,比如i-0e是不合法的。只能是 i0e。(规范没有规定整数的最大长度) Byte String 可以简单的理解为字符串(实际上是字节字符串 Byte String), 以字符串的长度开头,后面跟着一个冒号:,然后是字符串的内容,比如4:spam表示字符串spam。可以为空字符串,例如0:。(规范没有规定字符串的最大长度) List 就是列表,以l开头,以e结尾,中间是列表的元素,可以是任意Bencode类型的值,比如l5:hello5:worldi123e表示列表["hello", "world", 123]。可以为空列表,例如le。 Dictionary 就是字典,以d开头,以e结尾,中间是字典的键值对,其中键只能是Byte String类型,值可以是Bencode4中类型中的任意类型,比如d4:name4:john3:agei18ee表示字典{"name": "john", "age": 18}。 可以为空字典,例如de。 根据上面的规则,我们可以总结出如下的规律: Bencode无法编码浮点型数据,如果实在需要编码,可以用Byte String代替(二进制编码)。 Bencode的列表和字典是可以嵌套的,比如列表中的元素和字典中的值可以是任意Bencode基本类型。 Bencode的字典中的键必须是Byte String类型。 Bencode的优点 说完了Bencode的编码规则,我先说我认为Bencode的优点: Bencode的编码规则简单,满足了常用的数据类型,支持嵌套,可以表示复杂的数据结构,同时也可以编码二进制数据。 Bencode是顺序编码,每一种数据类型的起始和结束都是固定的,例如列表的起始是l,结束是e,字典的起始是d,结束是e,字节字符串的起始是字符串的长度,结束是字符串的内容,这样的编码方式,使得Bencode的解码非常简单,只需要按照顺序,就可以流式解析每一个字节,天然支持递归解析。节省内存的同时,也提高了解码的效率。 数据结构和JSON类似,但是比JSON更加简单。 Bencode的缺点 Bencode的缺点也是显而易见的,这是在我用Node.js以及Deno分别实现过一个Bencode编解码器之后发现的。 我认为Bencode的最大的缺点就是Byte String没有编解码规则。 Byte String 在原文档中的定义是:字节字符串,也就是二进制字符串,它的编码规则是:以字符串的长度开头,后面跟着一个冒号:,然后是字符串的内容,比如4:spam表示字符串spam。可以为空字符串,例如0:。 我们知道在使用utf-8编码的情况下,一个字节可以表示一个ASCII字符,而一个中文字符需要3个字节,所以在Bencode中,一个中文字符需要用3个字节来表示,比如3:中。 Bencode默认Byte String是二进制字节流,但是并没有规定具体的编码方式,社区默认Byte String的字节如果都是合法UTF-8字节,就解析成UTF-8字符串,如果包含非法UTF-8字节,就不解析,返回对应的字节数组。 所以这就导致了问题的出现,看下面两个例子: 1.当ByteString作为字典的值时 当ByteString作为字典的值时,如果此时的ByteString包含非UTF-8编码的字符,那么在解码的时候,就需要小心。 因为在Node.js和Deno中,使用TextDecoder解码一个字节数组时,如果字节数组中包含非UTF-8编码的字符,那解码后的字符串,就会丢失数据。 此时如果你再将解码后的字符串编码成字节数组,那么就会得到一个和原来的字节数组不一样的字节数组,这就导致了解码后的数据和编码前的数据不一致。 const bytes = Uint8Array.from([0xff, 0x32, 0x33, 0xe4]) const str = new TextDecoder().decode(bytes) const newBytes = new TextEncoder()....