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())
|