udp穿透的方法V2

背景是我23年写过一个udp穿透的方法,那时候手搓了一个小demo,细节见:

udp穿透的方法:https://blog.csdn.net/yeshennet/article/details/135165159

上周公司同事有在讨论这个,我又回头看了下我那时候写的脚本。现在有AI了,提了一下说明,生成了代码如下:

python 复制代码
# Server (Public IP)
import socket
import threading

clients = {}  # {client_id: (ip, port)}

def handle_client(sock):
    while True:
        try:
            data, addr = sock.recvfrom(1024)
            msg = data.decode()
            
            if msg.startswith("REGISTER:"):
                # Register client
                client_id = msg.split(":")[1]
                clients[client_id] = addr
                print(f"Registered: {client_id} -> {addr}")
                sock.sendto(b"OK", addr)
                
            elif msg.startswith("QUERY:"):
                # Query peer address
                target_id = msg.split(":")[1]
                if target_id in clients:
                    target_addr = clients[target_id]
                    # Send target address to requester
                    response = f"ADDR:{target_addr[0]}:{target_addr[1]}"
                    sock.sendto(response.encode(), addr)
                    # Notify target that someone wants to connect
                    notify = f"PEER:{addr[0]}:{addr[1]}"
                    sock.sendto(notify.encode(), target_addr)
                    print(f"Connect: {addr} <-> {target_addr}")
                else:
                    sock.sendto(b"NOT_FOUND", addr)
        except Exception as e:
            print(f"Error: {e}")

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(("0.0.0.0", 9999))
    print("Server started on port 9999")
    handle_client(sock)

if __name__ == "__main__":
    main()
python 复制代码
# Client A/B
import socket
import time
import threading

class UDPClient:
    def __init__(self, client_id, server_addr):
        self.client_id = client_id
        self.server_addr = server_addr
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.bind(("0.0.0.0", 0))
        self.sock.settimeout(0.5)  # Non-blocking receive with timeout
        
        self.peer_addr = None
        self.connected = False
        self.running = True
        
    def register(self):
        # Register to server
        msg = f"REGISTER:{self.client_id}"
        self.sock.sendto(msg.encode(), self.server_addr)
        print(f"Registered as {self.client_id}")
        time.sleep(0.5)
        
    def query_peer(self, peer_id):
        # Query peer address from server
        msg = f"QUERY:{peer_id}"
        self.sock.sendto(msg.encode(), self.server_addr)
        print(f"Querying {peer_id}...")
        
    def send_thread(self):
        # Thread for sending packets periodically
        last_send_time = 0
        while self.running:
            current_time = time.time()
            
            # Send packet every 1 second if not connected or as keepalive
            if self.peer_addr and (current_time - last_send_time >= 1.0):
                try:
                    msg = f"PING:{self.client_id}"
                    self.sock.sendto(msg.encode(), self.peer_addr)
                    last_send_time = current_time
                    if not self.connected:
                        print(f"Punching hole to {self.peer_addr}...")
                except Exception as e:
                    print(f"Send error: {e}")
            
            time.sleep(0.1)  # Small sleep to avoid busy loop
    
    def receive_thread(self):
        # Thread for receiving packets
        while self.running:
            try:
                data, addr = self.sock.recvfrom(1024)
                msg = data.decode()
                
                if msg.startswith("ADDR:"):
                    # Got peer address from server
                    parts = msg.split(":")
                    peer_ip = parts[1]
                    peer_port = int(parts[2])
                    self.peer_addr = (peer_ip, peer_port)
                    print(f"Got peer address: {self.peer_addr}")
                    
                elif msg.startswith("PEER:"):
                    # Server notified us that someone wants to connect
                    parts = msg.split(":")
                    peer_ip = parts[1]
                    peer_port = int(parts[2])
                    self.peer_addr = (peer_ip, peer_port)
                    print(f"Peer wants to connect: {self.peer_addr}")
                    
                elif msg.startswith("PING:"):
                    # Received ping from peer
                    if not self.connected:
                        self.connected = True
                        print(f"*** Hole punched! Connected to {addr} ***")
                    # Send pong back
                    pong_msg = f"PONG:{self.client_id}"
                    self.sock.sendto(pong_msg.encode(), addr)
                    
                elif msg.startswith("PONG:"):
                    # Received pong from peer
                    if not self.connected:
                        self.connected = True
                        print(f"*** Hole punched! Connected to {addr} ***")
                        
                elif msg.startswith("MSG:"):
                    # Normal message from peer
                    print(f"<< {msg[4:]}")
                    
            except socket.timeout:
                # Timeout, continue loop
                continue
            except Exception as e:
                if self.running:
                    print(f"Receive error: {e}")
    
    def run(self, peer_id):
        # Start threads
        recv_thread = threading.Thread(target=self.receive_thread, daemon=True)
        send_thread = threading.Thread(target=self.send_thread, daemon=True)
        
        recv_thread.start()
        send_thread.start()
        
        # Register and query peer
        self.register()
        time.sleep(1)
        self.query_peer(peer_id)
        
        # Wait for connection
        print("Waiting for connection...")
        while not self.connected:
            time.sleep(0.5)
        
        # Interactive chat
        print("You can now send messages (type 'quit' to exit):")
        try:
            while True:
                user_input = input("> ")
                if user_input.lower() == 'quit':
                    break
                if self.peer_addr:
                    msg = f"MSG:{user_input}"
                    self.sock.sendto(msg.encode(), self.peer_addr)
        except KeyboardInterrupt:
            pass
        
        self.running = False
        print("Shutting down...")

def main():
    import sys
    if len(sys.argv) != 4:
        print("Usage: python client.py <client_id> <peer_id> <server_ip>")
        print("Example: python client.py A B 1.2.3.4")
        return
    
    client_id = sys.argv[1]
    peer_id = sys.argv[2]
    server_ip = sys.argv[3]
    server_addr = (server_ip, 9999)
    
    client = UDPClient(client_id, server_addr)
    client.run(peer_id)

if __name__ == "__main__":
    main()

Usage

1. Start server on public IP:

bash 复制代码
python server.py

2. Start Client A (in LAN A):

bash 复制代码
python client.py A B <server_public_ip>

3. Start Client B (in LAN B):

bash 复制代码
python client.py B A <server_public_ip>

Key Features

  1. Non-blocking: Uses separate threads for sending and receiving
  2. Auto-retry: Sends UDP packet every 1 second when not connected
  3. Keepalive: Continues sending packets after connection for NAT mapping maintenance
  4. Python 3: Compatible with Python 3.x
  5. No Chinese: All comments and messages in English

本系列的其他文章

  1. udp穿透的方法:https://blog.csdn.net/yeshennet/article/details/135165159
  2. udp穿透的方法V2:https://blog.csdn.net/yeshennet/article/details/157170723
  3. TCP穿透的方法:https://blog.csdn.net/yeshennet/article/details/157170822
相关推荐
Jony_2 天前
高可用移动网络连接
网络协议
chilix3 天前
Linux 跨网段路由转发配置
网络协议
DianSan_ERP4 天前
电商API接口全链路监控:构建坚不可摧的线上运维防线
大数据·运维·网络·人工智能·git·servlet
呉師傅4 天前
火狐浏览器报错配置文件缺失如何解决#操作技巧#
运维·网络·windows·电脑
gihigo19984 天前
基于TCP协议实现视频采集与通信
网络协议·tcp/ip·音视频
2501_946205524 天前
晶圆机器人双臂怎么选型?适配2-12寸晶圆的末端效应器有哪些?
服务器·网络·机器人
linux kernel4 天前
第七部分:高级IO
服务器·网络
数字护盾(和中)4 天前
BAS+ATT&CK:企业主动防御的黄金组合
服务器·网络·数据库
~远在太平洋~4 天前
Debian系统如何删除多余的kernel
linux·网络·debian
unfeeling_5 天前
Keepalived实验
linux·服务器·网络