网络
设备可以连在一起,互相收发消息,这就叫网络。许多网络再互联,叫互联网;我们日常说的 Internet,就是「网络的互联网」。
网络编程并不简单,下面这个项目也体现了这一点。但美妙之处在于:它涵盖了你该了解的网络编程常见要素,同时又相当简单、好玩。
先设定场景……
连接
可以把网络想成一层层叠起来。最底层是通信的基础:必须有一条路,让信号从一台设备到另一台。有时用无线电,本例只用两根线。

在这之上,可以搭建网络协议栈的其余各层。
如示意图,蓝色和红色 micro:bit 用鳄鱼夹相连。两台都用引脚 1 输出、引脚 2 输入;一台的输出接另一台的输入。有点像拿电话听筒的方向——一端是麦克风(输入),一端是扬声器(输出)。你对着麦克风说话,对方听筒里就能听到。拿反了就会出怪声!
本例也一样:线必须接对!
信号
协议栈的下一层是信号,往往取决于物理连接的特性。这里就是通过 I/O 引脚在线上传送的开/关数字信号。
还记得可以这样操作 I/O 引脚:
pin1.write_digital(1) # 打开信号
pin1.write_digital(0) # 关闭信号
input = pin2.read_digital() # 读取信号(1 或 0)
下一步是约定如何使用和处理信号——这就需要……
协议
若你有幸见到女王,行为是有规矩的:她到场要鞠躬或屈膝礼,伸手要礼貌握手,先称「Your Majesty」再称「ma'am」……这套规矩叫皇家礼仪。协议就是:在特定情境下大家事先约定怎么行为,以免临场混乱。

计算机网络里同样需要协议,让计算机事先约定如何收发消息。最著名的也许是万维网用的 HTTP(超文本传输协议)。
比计算机更早的一种「发消息协议」是摩尔斯电码:用长短不一的开/关信号表示字符,常听成「嘀」「嗒」。长信号叫划(-),短信号叫点(.)。组合起来就能发字母,例如标准字母表:
.- A .--- J ... S .---- 1 ----. 9
-... B -.- K - T ..--- 2 ----- 0
-.-. C .-.. L ..- U ...-- 3
-.. D -- M ...- V ....- 4
. E -. N .-- W ..... 5
..-. F --- O -..- X -.... 6
--. G .--. P -.-- Y --... 7
.... H --.- Q --.. Z ---.. 8
.. I .-. R
按上表,发字母 H 就要连续四次短开信号(四个点 ....);发 L 也是四次,但第二次要长一些(.-..)。
显然时序很重要:要能区分点和划。这也是协议要约定的,这样大家的实现才能互通。本例约定:
- 持续时间 小于 250 毫秒 的信号为点。
- 250 毫秒到不足 500 毫秒 为划。
- 其他时长忽略。
- 信号间隔 大于 500 毫秒 表示一个字符结束。
于是字母 H 就是四个不超过 250 毫秒的「开」,再跟一段超过 500 毫秒的停顿。
消息
我们终于能构造消息——对人类有意义的内容,这是协议栈的最顶层。
用上面的协议,我可以在物理线上发送这样一串信号:
...././.-../.-../---/.--/---/.-./.-../-..
你能读出是什么吗?
应用
有了协议栈,还需要应用来和它交互——收发消息的界面。HTTP 虽重要,多数人交给浏览器,万维网的底层栈被藏起来了(本该如此)。
那 micro:bit 上该写什么应用?从用户角度该怎么用?
显然,发消息要能输入点和划(我们用按钮 A)。要看已发或刚收到的消息,应能触发滚动显示(用按钮 B)。既然是摩尔斯码,若接了扬声器,输入时还应播放「嘀嗒」作为听觉反馈。
最终程序
完整程序如下,注释较细,便于理解:
from bibo import *
import music
# 摩尔斯码与字符对照表
MORSE_CODE_LOOKUP = {
".-": "A",
"-...": "B",
"-.-.": "C",
"-..": "D",
".": "E",
"..-.": "F",
"--.": "G",
"....": "H",
"..": "I",
".---": "J",
"-.-": "K",
".-..": "L",
"--": "M",
"-.": "N",
"---": "O",
".--.": "P",
"--.-": "Q",
".-.": "R",
"...": "S",
"-": "T",
"..-": "U",
"...-": "V",
".--": "W",
"-..-": "X",
"-.--": "Y",
"--..": "Z",
".----": "1",
"..---": "2",
"...--": "3",
"....-": "4",
".....": "5",
"-....": "6",
"--...": "7",
"---..": "8",
"----.": "9",
"-----": "0"
}
def decode(buffer):
# 查表解码;没有则返回句号
return MORSE_CODE_LOOKUP.get(buffer, '.')
# 显示一个点
DOT = Image("00000:"
"00000:"
"00900:"
"00000:"
"00000:")
# 显示一个划
DASH = Image("00000:"
"00000:"
"09990:"
"00000:"
"00000:")
# 少于 250ms 为点
DOT_THRESHOLD = 250
# 少于 500ms 为划
DASH_THRESHOLD = 500
buffer = '' # incoming 摩尔斯缓冲
message = '' # 已解码字符
started_to_wait = running_time()
while True:
waiting = running_time() - started_to_wait
key_down_time = None
# 按住 A:发信号、蜂鸣
while button_a.is_pressed():
music.pitch(880, 10)
pin1.write_digital(1)
if not key_down_time:
key_down_time = running_time()
# 引脚 2 收到信号,当作 A 按下
while pin2.read_digital():
if not key_down_time:
key_down_time = running_time()
key_up_time = running_time()
pin1.write_digital(0)
if key_down_time:
duration = key_up_time - key_down_time
if duration < DOT_THRESHOLD:
buffer += '.'
display.show(DOT)
elif duration < DASH_THRESHOLD:
buffer += '-'
display.show(DASH)
else:
pass
started_to_wait = running_time()
elif len(buffer) > 0 and waiting > DASH_THRESHOLD:
character = decode(buffer)
buffer = ''
display.show(character)
message += character
if button_b.was_pressed():
display.scroll(message)
message = ''
你会怎样改进?能否改点、划的定义,让发码更快的人也能用?两台同时发会怎样?可以怎样处理冲突?