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
相关推荐
嗨 ! 海洋2 小时前
K8S创建pod,CNI插件的网络配置过程
网络·kubernetes·php
尼古拉斯·纯情暖男·天真·阿玮2 小时前
实验十一 动态主机配置(DHCP)实验
网络·智能路由器
michael_ouyang3 小时前
WebSocket心跳方案选型与最佳实践
网络·websocket·网络协议
23124_803 小时前
HTTP头注入
网络·网络协议·http
一条闲鱼_mytube4 小时前
智能体设计模式(五)人机协同-知识检索RAG-智能体间通信
网络·人工智能·设计模式
9稳4 小时前
基于单片机的家庭安全系统设计
开发语言·网络·数据库·单片机·嵌入式硬件
源远流长jerry4 小时前
DPDK 实现的轻量级 UDP 回显服务程序
linux·运维·服务器·网络·网络协议·ip
一路往蓝-Anbo4 小时前
第37期:启动流程(二):C Runtime (CRT) 初始化与重定位
c语言·开发语言·网络·stm32·单片机·嵌入式硬件
白狐_7984 小时前
【计网全栈通关】第 6 篇:网络层路由核心——RIP、OSPF 协议原理与 Cisco 配置实战
网络·智能路由器