互联网不是永远在线的。自然灾害断网、偏远地区无信号、审查封锁——这些场景下,传统即时通信工具全部失效。但数据交换的需求不会消失。近几年,围绕"去中心化""离线优先""mesh 网络"的替代通信方案逐渐成熟,有些已经可以直接上手部署。
传统通信的隐含前提
微信、Telegram、Slack 这类工具都依赖同一个前提:两端能访问同一台中心服务器。断网的那一刻,这个前提就不成立了。即便端到端加密保证了内容安全,连接本身仍然依赖基础设施。
真正需要关注的是两类问题:
- 连接层:没有 IP 网络,设备之间如何发现彼此、建立链路?
- 应用层:消息如何存储、转发、确认送达,而不依赖中心数据库?
下面几个方案从不同角度回答了这些问题。
LoRa Mesh:Meshtastic 的低功耗长距离通信
Meshtastic 是目前最活跃的 LoRa mesh 项目。每台设备是一个节点,自动与附近节点组网,消息沿 mesh 逐跳转发。典型场景:户外徒步团队每人携带一个 LoRa 模块,即使完全没有手机信号,群内消息仍然可达。
硬件成本很低——一个 ESP32 + LoRa 模块大约 50 元人民币。通信距离市区约 300 米,开阔地可达 5–10 公里。功耗极低,一节 18650 电池能跑数天。
# 安装 Meshtastic CLI(Python 包)
pip install meshtastic
# 查看已连接设备的当前配置
meshtastic --info
# 发送一条文本消息到默认频道
meshtastic --sendtext "所有人集合,坐标 N39.9 E116.3"
# 设置节点名称,方便 mesh 内识别
meshtastic --set-owner "救援A组-01"
# 查看 mesh 内可见的其他节点
meshtastic --nodes
Meshtastic 也提供 Python SDK,可以嵌入到自定义应用中:
import meshtastic
import meshtastic.tcp_interface
# 通过 TCP 连接到本地 Meshtastic 设备(设备先通过 USB 或 WiFi 连到主机)
interface = meshtastic.tcp_interface.TCPInterface(hostname="localhost")
# 发送消息
interface.sendText("传感器读数: 温度22°C 湿度65%", channelIndex=0)
# 获取节点列表
nodes = interface.nodes
for node_id, node_info in nodes.items():
print(f"节点: {node_info.get('user', {}).get('longName', '未知')} "
f"SNR: {node_info.get('snr', 'N/A')} "
f"距离: 约 {node_info.get('distance', '未知')}m")
interface.close()
注意:LoRa 通信速率极低(约 250 bit/s),只适合短文本和少量数据,不要试图传图片或文件。
Yggdrasil:覆盖式 Mesh 网络的 IPv6 互联
如果设备之间有某种物理链路(WiFi 直连、蓝牙、局域网),但没有公网 IP,Yggdrasil 可以在这些碎片化链路之上构建一个端到端的 IPv6 overlay 网络。每个节点自动获得一个 0200::/7 范围的 IPv6 地址,无需 DHCP,无需 NAT。
# Linux 上安装 Yggdrasil
sudo apt install yggdrasil-go # Debian/Ubuntu
# 或从 GitHub Releases 下载二进制
# 生成配置文件
sudo yggdrasil -genconf > /etc/yggdrasil/yggdrasil.conf
# 启动(默认会自动发现局域网内的其他节点)
sudo yggdrasil -useconffile /etc/yggdrasil/yggdrasil.conf
# 查看自己的 Yggdrasil IPv6 地址
yggdrasil -getaddress
# 输出类似: 200:5e22:943e:e1f2:fd5e:892:3b4a:7c
# 测试连通性——ping 另一个 Yggdrasil 节点
ping6 200:abcd:1234:5678:9abc:def0:1234:5678
Yggdrasil 的关键特性:节点之间只需一个可达的物理路径(哪怕只是局域网),overlay 层会自动路由。这意味着你可以在断网的小区内,让所有邻居的设备通过 WiFi 局域网组成一个独立的 Yggdrasil 网络,在上面跑 HTTP、SSH、任何 TCP/UDP 应用。
# /etc/yggdrasil/yggdrasil.conf 关键配置片段
# 如果有公网节点可做中继,填入 Peers;纯局域网场景则留空,节点会自动发现同网段邻居
Peers: []
# 或指定已知的中继节点:
# Peers:
# - tls://1.2.3.4:1234
Listen: []
# 如果想让其他节点通过 WiFi 局域网连入:
# Listen:
# - tcp://0.0.0.0:12345
IfName: "auto"
# auto 会自动创建虚拟网卡;也可指定如 "ygg0"
Briar:离线优先的端到端加密消息
Briar 专为高风险环境设计:记者、活动家、灾区居民。它的核心思路是——有网时用 Tor,没网时用蓝牙或 WiFi 直连,消息在本地存储并沿 mesh 逐跳传递。
Briar 是 Android 应用,直接从 F-Droid 或官网安装即可。没有服务器账号,身份完全本地生成。联系人通过面对面扫描二维码交换公钥——这个设计刻意避免了任何在线注册环节。
对于开发者,Briar 的消息传输协议(Bramble)有独立文档,可以参考其设计思路实现自己的离线消息系统:
- Bramble Transport Protocol:基于 Tor 的加密传输
- Bramble Sync Protocol:离线时的消息同步与去重
- Bramble Rendezvous Protocol:节点发现
自建离线消息中继:一个最小 Python 示例
如果上述方案都不适用(比如硬件受限、团队技术栈统一),可以用最简单的方式搭建一个局域网消息中继。下面是一个不到 50 行的 Python 脚本,任何设备连入同一 WiFi 即可使用:
"""
离线消息中继服务器 + 客户端
同一局域网内运行 server,其他设备用 client 发送/接收消息
无需公网,无需注册
"""
import socket
import json
import threading
import time
SERVER_HOST = "0.0.0.0" # 本机所有网卡
SERVER_PORT = 9900
MSG_FILE = "messages.json" # 本地持久化
def load_messages():
try:
with open(MSG_FILE, "r") as f:
return json.load(f)
except FileNotFoundError:
return []
def save_messages(msgs):
with open(MSG_FILE, "w") as f:
json.dump(msgs, f, ensure_ascii=False)
# ---------- 服务器 ----------
def handle_client(conn, addr):
data = conn.recv(4096).decode()
req = json.loads(data)
if req["action"] == "send":
msgs = load_messages()
msgs.append({"from": req["name"], "text": req["text"], "ts": time.time()})
save_messages(msgs)
conn.sendall(json.dumps({"status": "ok", "count": len(msgs)}).encode())
elif req["action"] == "recv":
since = req.get("since", 0)
msgs = load_messages()
new = [m for m in msgs if m["ts"] > since]
conn.sendall(json.dumps({"messages": new}).encode())
conn.close()
def run_server():
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srv.bind((SERVER_HOST, SERVER_PORT))
srv.listen(5)
print(f"中继服务器启动于 {SERVER_PORT},局域网内设备可连接")
while True:
conn, addr = srv.accept()
threading.Thread(target=handle_client, args=(conn, addr), daemon=True).start()
# ---------- 客户端 ----------
def send_message(name, text, server_ip):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server_ip, SERVER_PORT))
s.sendall(json.dumps({"action": "send", "name": name, "text": text}).encode())
resp = json.loads(s.recv(4096).decode())
print(f"发送成功,服务器当前共 {resp['count']} 条消息")
s.close()
def fetch_messages(since_ts, server_ip):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((server_ip, SERVER_PORT))
s.sendall(json.dumps({"action": "recv", "since": since_ts}).encode())
resp = json.loads(s.recv(8192).decode())
for m in resp["messages"]:
print(f"[{m['from']}] {m['text']}")
s.close()
return max((m["ts"] for m in resp["messages"]), default=since_ts)
# 使用方式:
# 在一台机器上: run_server()
# 其他机器上: send_message("张三", "集合点在北门", "192.168.1.100")
# fetch_messages(0, "192.168.1.100")
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("用法: python relay.py server | client <server_ip> send <名字> <消息> | client <server_ip> recv")
sys.exit(1)
mode = sys.argv[1]
if mode == "server":
run_server()
elif mode == "client":
sip = sys.argv[2]
action = sys.argv[3]
if action == "send":
send_message(sys.argv[4], sys.argv[5], sip)
elif action == "recv":
fetch_messages(0, sip)
这个示例只做最基本的消息存储和拉取,没有加密、没有去重、没有多跳转发。生产环境请参考 Briar 的 Bramble 协议设计,或直接使用 Meshtastic / Yggdrasil。
选型建议与风险清单
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 户外/灾区,无任何网络 | Meshtastic (LoRa) | 硬件便宜,长距离,低功耗 |
| 断网小区/营地,有 WiFi 局域网 | Yggdrasil | 跑任何 TCP 应用,自动组网 |
| 高安全需求,面对面交换身份 | Briar | 端到端加密,无服务器,离线可用 |
| 快速原型,团队已有局域网 | 自建中继 | 最简单,但需自己补加密 |
几个需要注意的风险:
- LoRa 频段合规:中国 433MHz 和 470MHz 频段有功率限制,发射功率超过 10mW 需要申请。Meshtastic 默认配置在合法范围内,不要随意调高功率。
- Mesh 网络规模:Meshtastic 单频道约支持 40–80 个活跃节点,Yggdrasil 理论无上限但延迟随跳数增长。超过 100 足节点的场景需要仔细测试。
- 物理安全:离线通信意味着设备本身是信任边界。Briar 的面对面密钥交换就是为了解决这个问题。自建中继方案中,任何连入局域网的设备都能读取消息——务必在应用层补 TLS 或消息级加密。
- 持久化风险:本地存储的消息在设备丢失时不可恢复。关键数据要考虑多设备冗余。
互联网不是唯一的通信方式,但每种替代方案都有自己的边界条件。选对工具的前提是搞清楚你的断网场景到底属于哪一类——是完全没有物理链路,还是有链路但没有公网路由。答案不同,方案完全不同。