remote/tools/rendezvous.py
yu d659a4e7f1 feat(tools): 打洞探针与极简 rendezvous 服务器(Python POC)
- punch_probe.py: UDP 打洞探针,支持 STUN 查映射、--peer 手工配对、--server/--room 信令配对
- rendezvous.py: 极简 UDP 信令服务器,只做房间配对与地址回推,不承载媒体
- 已在 47.98.245.66 实测跨公网注册/配对可用
2026-10-23 17:32:17 +08:00

91 lines
3.0 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""极简 rendezvous(信令)服务器 —— POC 用
作用只有一个:**告诉双方「对方看到的你」是什么地址**。
双方注册到同一个房间后,服务器把各自观察到的公网地址回推给对方,
之后双方直接打洞,服务器不再参与(也不承载任何媒体流量)。
用法(跑在有公网 IP 的机器上,比如 43.140.215.244):
py rendezvous.py --port 47999
客户端:
py punch_probe.py --server <服务器IP>:47999 --room test01 --port 47800
"""
import argparse
import socket
import sys
import time
# 房间成员多久没动静就清掉
MEMBER_TTL = 30.0
def fmt(sockaddr):
host, port = sockaddr[0], sockaddr[1]
return "[%s]:%d" % (host, port) if ":" in host else "%s:%d" % (host, port)
def main():
ap = argparse.ArgumentParser(description="极简 rendezvous 服务器")
ap.add_argument("--bind", default="0.0.0.0")
ap.add_argument("--port", type=int, default=47999)
args = ap.parse_args()
# 重定向到文件时 stdout 会全缓冲,日志要等进程退出才出现;强制行缓冲
try:
sys.stdout.reconfigure(line_buffering=True)
except (AttributeError, OSError):
pass
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((args.bind, args.port))
print("[rendezvous] 监听 udp://%s:%d" % (args.bind, args.port))
print("[rendezvous] 房间成员 TTL = %.0fs,Ctrl+C 退出\n" % MEMBER_TTL)
rooms = {} # room -> [(addr, last_seen)]
try:
while True:
data, src = sock.recvfrom(2048)
txt = data.decode("utf-8", "replace").strip()
now = time.time()
if not txt.startswith("REGISTER|"):
continue
room = txt.split("|", 1)[1].strip()
if not room:
continue
# 清掉过期成员 + 去掉本机的旧记录(支持重复注册)
members = [(a, t) for (a, t) in rooms.get(room, [])
if now - t < MEMBER_TTL and a[1] != src[1]]
members.append((src, now))
rooms[room] = members
print("[rendezvous] %s 注册 room=%s (现有 %d 人) 源地址=%s"
% (fmt(src), room, len(members), fmt(src)))
if len(members) >= 2:
a1, _ = members[0]
a2, _ = members[1]
sock.sendto(b"PEER|" + fmt(a2).encode(), a1)
sock.sendto(b"PEER|" + fmt(a1).encode(), a2)
print("[rendezvous] -> 配对成功:%s <==> %s"
% (fmt(a1), fmt(a2)))
else:
sock.sendto(b"WAIT", src)
print("[rendezvous] -> 等待第二个人加入 ...")
except KeyboardInterrupt:
print("\n[rendezvous] 退出")
finally:
sock.close()
return 0
if __name__ == "__main__":
sys.exit(main())