之前热播的良心剧《长安十二时辰》有很多出彩的设计,其中一个就是「望楼信令」。
在第一集中,李必给张小敬解释了望楼:
长安共一百零八坊,每300步设一望楼,望楼上武侯皆为朔方节度使所排弩兵,历经玉川战役,桑干河三捷,大破叶护部之战,目力精准,街巷动静皆在目。武侯自望楼看到长安任何异状,都以约定暗语速传靖安司。靖安司对任何一处所发命令,瞬息即可横跨整个长安城。此外,武侯皆身带臂张弩,可中二百三十步之内任意目标。
在第二集,东宫右卫率姚汝能简单解释了望楼传信的「约定暗语」:
信令灯符,以八卦卦象变化而来,设计繁复,短时间内难以记忆。望楼的通信武侯特别加以训练,才可快速转译。
实际上,真实的望楼本身仅有观敌预警功能,并没有用作复杂的远程通信。但是没想到,剧组玩high了,居然真设计了一套望楼信令加密与传递系统!
本文以「望楼信令」为基础,尝试以通俗易懂的语言来解释网络通信。本文不涉及任何专业词汇,力求看过《长安十二时辰》的人都能理解。
望楼传信流程
从官方给出的设计图可以看出,这套「望楼信令」包括:
- 用于发送信令的望楼指示灯
- 一套基于卦象变化的信令
- 密码本(示例中是《唐韵》)。密码本有两个作用
- 加解密消息。这个很好理解,如果明文发送,那么狼卫只需要盯着望楼看就知道消息了
- 中文字数量很多,而信令能表达的意思有限,将中文字转化为其所在密码本的卷、韵、字,才能通过信令表示出来
我们就以官方给出的图示来解释一下「望楼传信」的流程。
话说张小敬发现狼卫的窝点在昌明坊,但昌明坊附近没有望楼,所以张小敬让崔器到最近的望楼去通知靖安司调虎贲军前来支援。
崔器到达最近的望楼,将消息告知望楼武侯。武侯需要做如下几件事情:
- 根据崔器的消息「曹破延在昌明坊,速支援」,从《唐韵》中找到对应的字
- 根据字所在《唐韵》中的卷、韵、字,翻译为对应的信令
- 通过指示灯将信令发送出去
下一个望楼接收到消息后,直接按照接收到的信令,原样发送给下一个望楼,一直到靖安司处的望楼。此望楼武侯需要做如下几件事情:
- 记录下接收到的信令
- 根据接收到的信令翻译为卷、韵、字,到《唐韵》中找到对应的字
- 将消息告知李必
望楼回传
李必接到消息后,速让武侯去回张小敬消息:「已发兵支援」。武侯到达最近的望楼:
- 将消息通过《唐韵》转为对应的卷、韵、字
- 通过指示灯将信令发送给了下一个望楼
而张小敬这边,左等右等,就是等不到消息。于是独闯狼穴,卒!
为什么张小敬收不到李必的回信呢?
望楼是靖安司所设,外部信息都发给靖安司,所以在向靖安司发消息时,所有的望楼都知道该向哪里发送信令。但反过来,靖安司发出去的消息,望楼如何得知该向哪里发?
第一个望楼由于消息是从靖安司直接由通传送过来的,所以知道此消息是发给张小敬的。但是第二个望楼接到信令后,怎么知道是要传给张小敬的呢?
第二个望楼的武侯有两个方法来继续传送信令!
第一个办法,就是直接向其它方向发信令。如上图,信令是从南边的望楼传过来的,那就向西、东、北三个方向发。后面的望楼也照这个逻辑发送。导致的问题就是,信令死循环或信令最终又回到了靖安司。因为望楼武侯不知道最终要发送给谁,什么时候该停。很明显,此方法不可取。
第二个方法,对信令解密,根据信令发送消息。例如,这里的消息是:「已发兵支援」,望楼武侯根据前一个信令内容得知,此消息是李必回给张小敬的,要发到昌明坊张小敬处,所以武侯就向昌明坊方向发消息。最后昌明坊望楼接到信令后,直接通知张小敬,不再向后发送消息。此方法能将消息传到,但是每个望楼都要解密信令,同时还要理解信令内容,根据上下文来判断。这会导致三个问题:
- 一是时效性较差,因为需要解密理解内容
- 二是消息的保密性也降低了,因为每个望楼武侯的人都知道了消息
- 三是,如果根据上下文无法判定消息该发往哪里,那消息还是无法送达。
改进信令
可以看到,上面的两个方法都没办法保证将消息发送到张小敬手里。那该怎么办呢?其实很简单,想想我们是如何发送邮件的。只要把收件信息和消息一起传送出去就可以了。这里有三种方式:
第一个方式,就是将收件人和消息一起转换为信令发送出去。现在发送的消息就变成了:「张小敬收,已发兵支援」。此方法,需要武侯解密前面的几个字,提高了一点效率,但是还是需要武侯理解消息,否则不知道解密到哪才能知道发给谁,同时还需要知道张小敬目前在哪里。
第二个方式,就是将目标地址和消息一起转换为信令发送出去。现在发送的消息就变成了:「昌明坊,已发兵支援」。此方法,同样需要武侯解密前面的几个字,提高了一点效率。这里不需要武侯理解消息的意思,只要知道各个坊的名称即可。但是武侯只知道发送到哪个地方,却不知道发送给谁。
第三个方式,就是将目标地址和收件人以及消息一起转换为信令发送出去。现在发送的消息就变成了:「昌明坊:张小敬,已发兵支援」。此方法,昌明坊的望楼需要解码到接收人「张小敬」,其它望楼只需要解码到「昌明坊」即可。
很明显,第三种方式是最合适的。但是,依然有些问题:
- 首先,此方式还是需要望楼武侯理解消息的意思。比如:所有望楼的武侯都需要知道各个坊的名称,且需要知道该向哪一个望楼传递信令;而昌明坊的望楼武侯需要事先知道张小敬是人名。
- 其次,事先不知道要解码多少个字。武侯需要一个个的解密出来,看了结果以后才知道还要不要再解密了。
该如何解决这两个问题呢?我们依然可以回想一下我们发邮件的过程。信封上有对应的位置来填写收件人和地址,所以我们可以固定这两个内容。
首先,我们可以对一百零八坊进行编号,例如:001,002,003。这样有两个好处:
- 此部分可以不用密码本,简化了编解码的复杂度,此消息即使被破解了,也无大碍,因为只知道消息是发到哪个望楼编号的(如果狼卫不知道望楼编号,也就不知道信令是发送到哪里的了)
- 同时,可以事先确定望楼需要解码的长度,即3个字。现在望楼接到信令,先解码前三个字,如果不是发给自己的,就直接转发给下一个望楼。如果是发给自己的,就继续向后解码。但是,这里该向后解码几个字呢?
一般人名都是两到三个字,但是也有可能有特殊情况,有些人的名字可能比较长。这里我们可以折中一下,设置5个字,即人名最长可以有5个字。当望楼发现此信令是发送给自己的,就再向后解5个字,找到收件人名。但是这里的张小敬只有三个字,多出来的两个字填什么呢?我们可以设定「空字」,即「张小敬--」。
改进后的望楼回传
现在李必回张小敬消息的流程就像下面这样:
- 李必让武侯去回张小敬消息:「已发兵支援」
- 武侯到达最近的望楼,将消息通过《唐韵》转为对应的卷、韵、字。同时,在前面加8个字「098张小敬--」(假设昌明坊编号是098)
- 通过指示灯将信令发送给了下一个望楼
- 下一个望楼接收到信令,解码前三个字,得到098。如果不是自己的编号,就发送给下一个望楼;如果是自己的望楼,就解码后面的名字。最后通过武侯通知张小敬
流程貌似是通了。不过稍等,想一想,当其中一个望楼接到信令,解码得到098,知道要发送到昌明坊望楼,但是它如何知道下一个该发送的望楼是哪个呢?
你可能会说,不就108个望楼吗?建完之后根据地理位置画张路线图就可以了。
但是,假如有1080个坊呢?或者后面又增设了几个坊呢?每次调整都需要重新再画路线图吗?
有没有什么简单的方法呢?
自学地址表
假设,我们到一个陌生的城市旅游,想去当地的著名旅游景点,但是没有手机,没有地图,我们该怎么办?我们当然是会找当地人询问该怎么走啊!
这里也是同样的道理。当一个望楼接收到信令后,发现要发送给昌明坊,但是不知道要发给哪个望楼。那么望楼武侯可以先广播一个「问路信令」,即此信令发送给四周的望楼,周边的望楼如果知道就回复,如果不知道,就再继续广播出去。直到昌明坊的望楼接到信令后,回复消息。回复的消息按原路返回,直到回到发出「问路信令」的望楼。
望楼接收到回复后,将下一个望楼地址记录到「地址表」中,比如:昌明坊-北望楼。然后向北边的望楼发送消息信令。当下次有信令过来时,如果还是发送到昌明坊的信令,那么直接从「地址表」中就能查到应该向哪个望楼传递信令了。
眼尖的你,应该发现了一个问题。广播出去的「问路信令」可能会死循环。
这里的一个解决方案就是,在「问路信令」中加入源地址信息,例如:028。当028望楼又接到这个信令的时候,就不再进行广播了。
多条信令
在电视剧中,信令是一条一条的发送出去的。假设现在有这么一个场景。李必同时发了两条消息:
- 一条消息是发给张小敬的回复:「已发兵支援」
- 一条消息是发给虎贲军的消息:「昌明坊捉狼」
望楼接到这两个消息后,该怎么处理呢?我们按照上面的流程来尝试发送一下:
- 望楼武侯将消息通过《唐韵》转为对应的卷、韵、字。
- 在发送给张小敬的消息前面加上「098张小敬--」(假设昌明坊编号是098)
- 在发送给虎贲军的消息前面加上「043虎贲军--」(假设虎贲军所在地编号是043)
- 通过指示灯将信令按顺序发送给了下一个望楼,假设发给张小敬的消息在前,同时两条信息需要传递的下一个望楼是北望楼。
- 下一个望楼接收到信令,解码前三个字,得到098。如果不是自己的编号,就发送给下一个望楼
- 最后接到消息的望楼解码一看,消息是「098张小敬--已发兵支援043虎贲军--昌明坊捉狼」
- 张小敬卒~
为什么会发生这种情况呢?因为消息与消息之间没有间隔,接收信令的望楼武侯无法确认接收到的信令到哪里结束。发送一条信令的时候还好,因为发完就结束了。但是发多条信令的时候,就会导致信息混乱。
解决方法有很多!
最简单的方案就是在发送完一个消息后,望楼就停止一段时间,然后再发送下一条信令。但是,这会导致信令传递的效率较低。可能就因为这段时间的延迟,张小敬凉凉了。
另一种方案是参照上面名字的设计,即发送定长的消息。例如,消息长度最长为10个字,以空字补位。上面的信令可以表示为「098张小敬--已发兵支援-----043虎贲军--昌明坊捉狼-----」。但是这对消息有很大的限制,消息不像名字的长度那样基本是固定的。
还有一种方法是,定义一个特殊的字符为「停止符」,当望楼接收到「停止符」后,就知道一条消息结束了。假设以#作为停止符,那么前面的消息可以表示为:「098张小敬--已发兵支援#043虎贲军--昌明坊捉狼#」
最后一种方案就是修改信令头,在信令头上加上消息的长度,例如:「098005张小敬--已发兵支援043005虎贲军--昌明坊捉狼」,其中的005表示消息有5个字。下一个望楼解码前6个字,其中前3个字确认地址,后3个字确认消息长度。然后再接收到5个字后(除去接收人的那5个字),就知道这条消息结束了。
完整流程
假设我们选择了「停止符」的方式来发送消息,下面以李必同时向张小敬和虎贲军发消息为例,来看一下完整的流程:
- 望楼武侯将消息通过《唐韵》转为对应的卷、韵、字。同时添加消息头,组成两条消息「098张小敬--已发兵支援#」,「043虎贲军--昌明坊捉狼#」
- 然后望楼武侯查了一下「地址表」,发现虎贲军还不在地址表里,所以就广播了一条「问路信令」
- 而昌明坊在「地址表」中有记录:昌明坊-北望楼,故直接向北边的望楼发送信令
- 此时收到了「问路信令」回复,也是发北望楼望楼,武侯将此信息记录到「地址表」,并向北边的望楼发送信令
- 北望楼接收到信令,解码前3个字,得到目标地址的编码,继续向后传递信令。
- 最后昌明坊望楼解码前3个字,发现是发送给自己的,解码后面的内容,得知此消息是给张小敬的,差通传将消息告知张小敬
- 虎贲军望楼解码前3个字,发现是发送给自己的,解码后面的内容,得知此消息是给虎贲军的,差通传通知虎贲军发兵支援
- 狼卫灭~
总结
本文通过《长安十二时辰》的望楼信令描述了消息的发送与接收过程。网络通信方式和此类似,只是复杂得多。参照上面的流程,回想一下我们发电子邮件的过程,你应该能理解网络是如何运行的!
参考资料
- 望楼加密系统:m.weibo.cn/status/4389…?
- 望楼传递系统:m.weibo.cn/status/4389…?
- 《图解TCP/IP》