10.8. 使用 WebSocket 实现双向控制¶
Server-Sent Events 只能单向推送。当拥有者在仪表盘上点击“立即保存一张快照”或“重置触发计数器”时,仪表盘必须向摄像头发送一条消息。这就是 WebSocket——一个 TCP 套接字,分帧的消息在两个方向上流动。
10.8.1. /control 路由¶
microdot.websocket.with_websocket() 会执行 WebSocket 升级握手,并把一个 WebSocket 对象交给处理函数。处理函数会无限循环,读取命令并发送确认:
from microdot.websocket import with_websocket
from microdot.websocket import WebSocketError
import json
@app.get('/control')
@with_websocket
async def control(request, ws):
while True:
try:
msg = await ws.receive()
except WebSocketError:
break
try:
cmd = json.loads(msg)
except ValueError:
await ws.send({'error': 'bad json'})
continue
if cmd.get('cmd') == 'snapshot_now':
if latest_jpeg:
path = '/sdcard/snaps/manual-{}.jpg'.format(
int(time.time()))
with open(path, 'wb') as f:
f.write(latest_jpeg)
await ws.send({'ok': True, 'saved': path})
else:
await ws.send({'ok': False, 'reason': 'no frame yet'})
elif cmd.get('cmd') == 'reset':
state['trigger_count'] = 0
await ws.send({'ok': True, 'counters': 'reset'})
else:
await ws.send({'error': 'unknown command'})
receive() 对于文本帧返回字符串,对于二进制帧返回字节。浏览器端的 WebSocket.send(...) 默认发送文本,所以采用 JSON 编码的命令是顺理成章的选择。
send() 接受字符串、字节,或任何可 JSON 序列化的对象——一个字典会作为 JSON 文本帧发送。
当客户端断开连接时(正常关闭、网络中断或协议错误),会抛出 WebSocketError。处理函数随即退出循环并返回;microdot 会清理这个套接字。
10.8.2. 仪表盘发送命令¶
两个按钮被放进 index.html,就在滑块旁边:
<button id="snap-btn">Save snapshot</button>
<button id="reset-btn">Reset counter</button>
而 app.js 会打开一次 WebSocket,并把两个按钮都接到 send:
const proto = location.protocol === 'https:' ? 'wss://' : 'ws://';
const ws = new WebSocket(proto + location.host + '/control');
document.getElementById('snap-btn').addEventListener('click', () =>
ws.send(JSON.stringify({cmd: 'snapshot_now'})));
document.getElementById('reset-btn').addEventListener('click', () =>
ws.send(JSON.stringify({cmd: 'reset'})));
ws.addEventListener('message', (e) => {
console.log('cam:', JSON.parse(e.data));
});
ws:// 与 wss:// 之间的选择,正好对应 http:// 与 https://——WebSocket 沿用相同的 TLS 处理方式。在启用 HTTPS 后,仪表盘会自动通过 wss:// 连接。
10.8.3. 何时选择 SSE,何时选择 WebSocket¶
当摄像头负责推送、而浏览器只负责接收时,请使用 SSE——例如通知、遥测、状态变化。传输线路就是普通的 HTTP,客户端代码只需一行(new EventSource),并且会自动重连。
当浏览器也需要主动推送时,请使用 WebSocket——例如按钮、以按键速率发送更新的滑块,以及任何等待下一次请求都会太慢的场景。连接是双向且分帧的,但两端的 API 都更复杂一些。
现在摄像头成了一个可交互的东西——观看画面、向外推送事件、接收传入的命令。