- punch_probe.py: UDP 打洞探针,支持 STUN 查映射、--peer 手工配对、--server/--room 信令配对 - rendezvous.py: 极简 UDP 信令服务器,只做房间配对与地址回推,不承载媒体 - 已在 47.98.245.66 实测跨公网注册/配对可用
91 lines
3.0 KiB
Python
91 lines
3.0 KiB
Python
#!/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())
|