You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需端口转发的P2P多人联机工作原理是什么?如何用Python模拟实现?

P2P网络无需端口转发的实现原理

前置背景:NAT拦截的本质

家庭/企业内网的路由器普遍采用NAPT(网络地址端口转换)技术,所有内网设备共享同一个公网IP。路由器会维护一张会话映射表,只有当内网设备先主动向某公网地址发起过请求,路由器才会生成对应的「内网IP:端口 <-> 公网IP:端口 <-> 目标公网地址:端口」的映射条目,后续该目标地址发来的数据包才会被放行到对应的内网设备;没有对应映射条目的外来数据包会被直接拦截,这也是C/S架构下服务端需要手动做端口转发的原因。

P2P打洞的核心逻辑

P2P能绕过NAT拦截靠的是**NAT穿透(俗称打洞)**技术,整个过程需要一台有公网IP的中继服务器做协调,步骤如下:

  • 所有准备加入P2P网络的对等节点,先主动和公网中继服务器建立连接,中继服务器会记录每个节点的公网IP和端口,同时每个节点的路由器已经生成了对应中继服务器地址的映射条目。
  • 当节点A需要和节点B建立通信时,先向中继服务器发送连接B的请求,中继服务器会把B的公网IP+端口返回给A,同时把A的公网IP+端口推送给B。
  • 节点A和B同时向对方的公网IP+端口发送探测数据包:此时A发往B的数据包虽然第一次会被B的路由器拦截,但B已经主动向A的地址发过探测包,B的路由器已经生成了允许A地址的数据包进入的映射条目,后续A再发的数据包就会被正常放行到B的内网设备,反之亦然。
  • 打洞成功后,两个节点就可以直接进行点对点通信,不需要再经过中继服务器转发数据。
    你提到的《深岩银河》这类P2P游戏就是用的这个逻辑,游戏厂商会提前部署公网的中继服务器做节点协调,玩家不需要手动配置端口转发。

Python Socket实现简单P2P连接演示

以下用UDP Socket实现极简的P2P打洞演示,需要你提前准备一台有公网IP的服务器运行中继服务。

1. 中继服务器代码 relay_server.py

import socket

# 中继服务配置,端口需要在服务器的安全组/防火墙放开
RELAY_HOST = "0.0.0.0"
RELAY_PORT = 9999

peer_map = {}  # 存储节点ID对应的公网地址

if __name__ == "__main__":
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((RELAY_HOST, RELAY_PORT))
    print(f"中继服务已启动,监听端口{RELAY_PORT}")

    while True:
        data, addr = sock.recvfrom(1024)
        msg = data.decode().strip()
        # 节点注册:格式为 register|节点ID
        if msg.startswith("register|"):
            peer_id = msg.split("|")[1]
            peer_map[peer_id] = addr
            print(f"节点{peer_id}已注册,地址{addr}")
        # 连接请求:格式为 connect|己方ID|目标ID
        elif msg.startswith("connect|"):
            _, self_id, target_id = msg.split("|")
            if target_id not in peer_map:
                sock.sendto(b"error|目标节点不存在", addr)
                continue
            # 把双方地址发给对方
            target_addr = peer_map[target_id]
            self_addr = peer_map[self_id]
            sock.sendto(f"peer|{target_id}|{target_addr[0]}|{target_addr[1]}".encode(), self_addr)
            sock.sendto(f"peer|{self_id}|{self_addr[0]}|{self_addr[1]}".encode(), target_addr)

2. 对等节点代码 p2p_peer.py

import socket
import threading

# 替换成你自己的中继服务器公网IP和端口
RELAY_HOST = "你的中继服务器公网IP"
RELAY_PORT = 9999

def recv_handler(sock):
    while True:
        data, addr = sock.recvfrom(1024)
        msg = data.decode().strip()
        if msg.startswith("peer|"):
            _, peer_id, peer_ip, peer_port = msg.split("|")
            peer_port = int(peer_port)
            print(f"收到节点{peer_id}的连接信息,地址为{peer_ip}:{peer_port},正在打洞...")
            # 先发一个探测包打洞
            sock.sendto(b"hole_punch", (peer_ip, peer_port))
        elif msg == "hole_punch":
            print(f"打洞成功,对方地址{addr},现在可以直接发消息了")
        else:
            print(f"收到消息:{msg},来自{addr}")

if __name__ == "__main__":
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 启动接收线程
    threading.Thread(target=recv_handler, args=(sock,), daemon=True).start()
    
    # 先注册到中继服务器
    self_id = input("输入你的节点ID:")
    sock.sendto(f"register|{self_id}".encode(), (RELAY_HOST, RELAY_PORT))
    print("注册成功,输入 connect 目标ID 发起连接:")
    
    while True:
        input_msg = input("> ")
        # 连接命令格式:connect 目标ID
        if input_msg.startswith("connect "):
            target_id = input_msg.split(" ")[1]
            sock.sendto(f"connect|{self_id}|{target_id}".encode(), (RELAY_HOST, RELAY_PORT))
        # 打洞成功后直接输入内容即可发送给对端
        else:
            # 替换成打洞成功后提示的对端公网IP和端口
            target_addr = ("对端公网IP", 对端端口)
            sock.sendto(input_msg.encode(), target_addr)

使用说明

  1. 先在公网服务器运行relay_server.py,确保9999端口已在防火墙/安全组放开
  2. 在内网的两台设备上分别运行p2p_peer.py,分别输入不同的节点ID完成注册
  3. 在任意一台设备上输入connect 对方节点ID,等待打洞成功提示
  4. 打洞成功后把代码里的target_addr替换成提示的对端地址,即可直接点对点发送消息

内容的提问来源于stack exchange,提问作者Matt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 10:06:02