#!/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())