好的,我们来聊聊P2P通信标准协议系列的第一个——STUN(Session Traversal Utilities for NAT)


P2P通信标准协议(一) — STUN


1. 什么是STUN?

STUN 是一种网络协议,全称是 Session Traversal Utilities for NAT,它的作用是帮助客户端设备在 NAT(网络地址转换)或防火墙后面,确定自己的公网 IP 地址和端口,从而实现点对点(P2P)通信。

简单来说,就是告诉你的设备“你在互联网外面长什么样”,方便两个设备直接连通。


2. 为什么需要STUN?

  • 现代网络环境中,绝大多数设备都在 NAT 后面,使用的是私有 IP 地址,这样它们之间不能直接通信。
  • P2P应用(比如视频通话、文件共享、游戏)需要知道对方的公网地址,才能建立直接连接。
  • 直接连接可以减少服务器负载,降低延迟,节省带宽。

3. STUN的工作原理

  1. 客户端向STUN服务器发送请求
    设备向公网的 STUN 服务器发出请求,询问“我现在的公网 IP 和端口是多少?”
  2. STUN服务器返回映射的公网地址
    服务器收到请求后,会在请求的报文中记录下客户端请求的公网IP和端口,然后返回给客户端。
  3. 客户端获知公网地址
    客户端根据返回的地址,知道自己映射到公网的 IP:Port。
  4. P2P连接辅助
    双方都获知各自的公网地址后,可以尝试直接连接对方(结合其他技术如ICE、TURN进行穿透)。

4. STUN协议的特点

  • 简单高效:协议轻量,报文很小,通信过程快。
  • 只提供地址发现功能,不负责数据转发。
  • 一般配合ICE(Interactive Connectivity Establishment)使用,作为建立连接的第一步。
  • UDP为主,因为UDP更适合实时通信且易于穿透NAT。

5. STUN协议消息结构

  • 请求(Request):客户端发出请求。
  • 响应(Response):服务器返回客户端公网映射地址。
  • 错误响应(Error Response):如果请求有误,服务器会返回错误代码。

6. STUN的局限和补充

  • STUN只能帮助发现公网地址,但不保证能成功穿透所有类型的NAT。
  • 对于对称NAT,直接P2P连接较难,通常需要TURN服务器中继转发数据。
  • 因此STUN常与TURN、ICE协议配合使用,统称为 “NAT穿透三剑客”

7. STUN协议标准

  • STUN的RFC是 RFC 5389(2010年发布)
  • 该RFC定义了STUN的协议细节,消息格式和交互方式。

总结

名称作用局限常见搭配
STUN探测客户端公网IP和端口不能穿透所有NATTURN(中继)、ICE
TURN中继服务器,转发数据服务器资源消耗大
ICE连接建立策略,使用STUN/TURN复杂度较高

好!下面给你简单示范一下 STUN 协议的基本交互流程示例,以及补充介绍 ICE 和 TURN,帮你理解三者如何协同实现 NAT 穿透和 P2P 通信。


一、STUN 协议交互示例(简化版)

假设你是客户端,想知道自己的公网 IP 和端口。


1. 客户端发送 STUN 请求

  • 协议基于 UDP。
  • 客户端向已知的 STUN 服务器(比如 stun.l.google.com:19302)发一个请求包。
  • 请求包结构遵循 RFC 5389,里面有随机 Transaction ID。

2. STUN 服务器接收请求,记录客户端公网 IP:Port

服务器从UDP包头看到的源地址,就是客户端在公网的地址。


3. 服务器返回响应包

  • 响应包包含 MAPPED-ADDRESS 属性,告诉客户端自己在公网的 IP 和端口。

4. 客户端解析响应,获得自己的公网地址


简单伪代码演示:

client_socket = UDP socket

// 1. 构造STUN请求包(含Transaction ID)
stun_request = build_stun_request()

// 2. 发送到STUN服务器
client_socket.sendto(stun_request, ("stun.server.ip", 19302))

// 3. 等待响应
response = client_socket.recv()

// 4. 解析响应包,提取 MAPPED-ADDRESS
public_ip, public_port = parse_mapped_address(response)

print("My public IP:", public_ip)
print("My public Port:", public_port)

二、ICE(Interactive Connectivity Establishment)

  • ICE是一个框架,结合STUN和TURN帮客户端自动寻找最佳路径建立P2P连接。
  • 流程大致是:
    • 收集候选地址(本地IP、STUN获得的公网IP、TURN服务器地址)
    • 交换候选地址
    • 测试候选地址连通性(ping对方)
    • 选择最优路径建立连接

三、TURN(Traversal Using Relays around NAT)

  • 如果 NAT 过于严格,STUN不能穿透,P2P连接失败。
  • TURN服务器作为中继,转发双方的数据流。
  • 性能开销大,但保证通信连通。

总结配合图示(文字版)

Client A                        Client B
   |                              |
   |------ STUN request --------->|   (to STUN Server)
   |<----- STUN response ---------|   (public IP:Port)
   |                              |
   |<---- ICE Candidate Exchange ->|
   |------ Connectivity Checks -->|
   |                              |
   |<------- Peer-to-Peer --------> (如果NAT能穿透)
   |                              |
   |<----- TURN Relay Data ------->| (否则通过TURN转发)

好,下面给你用 JavaScript + WebRTC 写一个基础示例,演示如何利用 STUN/ICE 建立 P2P 连接。WebRTC 内置了 STUN 和 ICE,调用很方便,示例主要演示最核心的部分。


WebRTC 利用 STUN/ICE 建立P2P连接基础示例

// 创建RTCPeerConnection,指定STUN服务器
const configuration = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' } // Google免费STUN服务器
  ]
};

const pc = new RTCPeerConnection(configuration);

// 监听ICE候选产生事件,发送给对端
pc.onicecandidate = event => {
  if (event.candidate) {
    console.log('New ICE candidate:', event.candidate);
    // TODO: 通过信令服务器发送candidate给对端
  }
};

// 创建数据通道(可选,做点对点数据传输)
const dataChannel = pc.createDataChannel('chat');

dataChannel.onopen = () => console.log('Data channel open');
dataChannel.onmessage = e => console.log('Received message:', e.data);

// 处理对方发来的ICE候选
function onRemoteIceCandidate(candidate) {
  pc.addIceCandidate(new RTCIceCandidate(candidate));
}

// 处理对方发来的Offer或Answer
async function onRemoteDescription(desc) {
  await pc.setRemoteDescription(desc);

  if (desc.type === 'offer') {
    const answer = await pc.createAnswer();
    await pc.setLocalDescription(answer);
    // TODO: 发送answer给对端
  }
}

// 发起连接流程(作为Caller)
async function startConnection() {
  const offer = await pc.createOffer();
  await pc.setLocalDescription(offer);
  // TODO: 发送offer给对端
}

// 下面是伪代码,展示信令服务器消息传递流程
// 假设用WebSocket或其它方式交换信令数据

/*
websocket.onmessage = async (message) => {
  const data = JSON.parse(message.data);
  if (data.offer) {
    await onRemoteDescription(data.offer);
  } else if (data.answer) {
    await onRemoteDescription(data.answer);
  } else if (data.candidate) {
    onRemoteIceCandidate(data.candidate);
  }
};
*/

// 开始连接(Caller调用)
startConnection();

说明

  • STUN服务器帮助获取公网地址,支持ICE候选地址收集。
  • 需要信令服务器(WebSocket等)在两端交换 offer/answer 和 ICE candidates。
  • 以上代码只示范了本地逻辑,信令交换需你自己实现。
  • 建立成功后,双方可以通过 dataChannel 实现实时P2P数据通信。