P2P 技术及其应用
我们知道,常规的视频直播通常是通过 rtsp
或者 hls
协议直播的,这种直播的特点是所有的流量都需要从中心服务器发出,经过 CDN(Content Delivery Network)
,下发到各个终端。
这种直播适合大量人群观看,但是服务器的带宽成本也很高。
为了节约成本,基于 P2P 技术的音视频直播应运而生,且已广泛应用于在线聊天(webRTC),网络摄像机等领域。
那么什么是 P2P 技术呢?这种技术又如何应用于视频直播呢?
P2P 技术
对等式网络(peer-to-peer
, 简称 P2P),又称点对点技术,是无中心服务器、依靠用户群(peers)交换信息的互联网体系,它的作用在于,减低以往网路传输中的节点,以降低资料遗失的风险。与有中心服务器的中央网络系统不同,对等网络的每个用户端既是一个节点,也有服务器的功能,任何一个节点无法直接找到其他节点,必须依靠其户群进行信息交流。
听上去和 Git 有着异曲同工之妙。
所以,只要知道了对方的 IP 地址,就能直接连接了吗?
在没有配置 NPTv6(Network Prefix Translation for IPv6)
的 IPv6 网络下,如果知道了对方的 IPv6 地址,是可以直接连接的,IPv6 地址通常是全球唯一的,因此每个设备可以直接在 Internet 上具有全局可达的 IPv6 地址。
但是,在 IPv4 网络环境下,无法直接连接,因为存在 NAT(Network Address Translation)
。
NAT 及分类
NAT 技术是为了解决 IPv4 地址短缺问题而提出的。IPv4 地址是 32 位二进制数,可以表示的地址数量是 2 的 32 次方,也就是 4294967296
个。然而,其中一部分地址被保留用于特殊用途,比如私有地址、广播地址、多播地址等,因此实际可用的IPv4地址数量要比这个数字少一些。此外,IPv4 地址的分配也不是均匀的,有些地区和组织分配到的地址比其他地区和组织多,这也导致了 IPv4 地址的短缺问题。
NAT 技术通过将内部网络的私有 IP 地址转换为公共 IP 地址,使得多个内部主机可以共享一个公共 IP 地址访问互联网,从而解决了 IPv4 地址短缺的问题。此外,NAT 技术还可以提高网络安全性,因为它可以隐藏内部网络的 IP 地址,使得外部网络无法直接访问内部网络。
NAT 有三种类型:
- 静态 NAT,静态一对一的地址转换。需要手动配置映射关系,若网路拓扑改变,则需更新配置,管理与维护难度大。通常用于企业内部网络
内网 IP | 外网 IP |
---|---|
192.168.1.55 | 219.152.168.222 |
192.168.1.59 | 219.152.168.223 |
192.168.1.155 | 219.152.168.224 |
动态 NAT(
Pooled NAT
),动态地址池转换。内部网络的私有 IP 地址转换时从一个地址池中选取公有 IP 地址,对应关系有租期限制 内网 IP 和外网 IP 也是一对一的映射关系,和静态 NAT 的区别就是映射关系会动态改变NAPT(
Network Address Port Translation
,端口地址转换):是一种特殊的动态 NAT,它不仅将内部主机的IP地址映射到一个公共 IP 地址,还将内部主机的端口号映射到公共 IP 地址的不同端口号上,以实现多个内部主机共享一个公共 IP 地址访问外部网络。
内网 IP | 外网 IP |
---|---|
192.168.1.55:5555 | 219.152.168.222:9200 |
192.168.1.59:80 | 219.152.168.223:9201 |
192.168.1.155:4456 | 219.152.168.224:9202 |
家用路由器一般使用 NAPT,拥有复杂网络的大型公司内网则会使用 Static NAT
和 Dynamic NAT
,本文讨论的 NAT 是第三种 NAT。
锥型和对称型 NAT
第三种 NAPT 又可以分为锥型和对称型 NAT (Symmetric NAT
)。锥型 NAT 又可以细分为 Full Cone NAT
, Address Restricted Cone NAT
, Port Restricted Cone NAT
,每一种 NAT 对于外部请求的响应方式有所不同。
Full Cone NAT
,完全锥型
一旦内部地址(iAddr:iPort
)映射到外部地址(eAddr:ePort
),所有发自 iAddr:iPort
的数据包都经由 eAddr:ePort
向外发送。任意外部主机都可以通过发送数据包给 eAddr:ePort
给 NAT设备内部的 iAddr:iPort
主机。
Address Restricted Cone NAT
,地址受限型
在完全锥型 NAT 的基础上限制了 IP 地址。只有和内部地址有过通信的外部地址,才能发送消息
Port Restricted Cone NAT
,端口受限型
在地址受限型 NAT 的基础上限制了端口,特定外部地址的特定端口,才能发送消息
Symmetric NAT
,对称型
可以用如下代码在 Chrome 浏览器里检测当前网络的 NAPT 的类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// parseCandidate from https://github.com/fippo/sdp
function parseCandidate(line) {
var parts;
// Parse both variants.
if (line.indexOf('a=candidate:') === 0) {
parts = line.substring(12).split(' ');
} else {
parts = line.substring(10).split(' ');
}
var candidate = {
foundation: parts[0],
component: parts[1],
protocol: parts[2].toLowerCase(),
priority: parseInt(parts[3], 10),
ip: parts[4],
port: parseInt(parts[5], 10),
// skip parts[6] == 'typ'
type: parts[7]
};
for (var i = 8; i < parts.length; i += 2) {
switch (parts[i]) {
case 'raddr':
candidate.relatedAddress = parts[i + 1];
break;
case 'rport':
candidate.relatedPort = parseInt(parts[i + 1], 10);
break;
case 'tcptype':
candidate.tcpType = parts[i + 1];
break;
default: // Unknown extensions are silently ignored.
break;
}
}
return candidate;
};
var candidates = {};
var pc = new RTCPeerConnection({iceServers: [
{urls: 'stun:stun1.l.google.com:19302'},
{urls: 'stun:stun2.l.google.com:19302'}
]});
pc.createDataChannel("foo");
pc.onicecandidate = function(e) {
if (e.candidate && e.candidate.candidate.indexOf('srflx') !== -1) {
var cand = parseCandidate(e.candidate.candidate);
if (!candidates[cand.relatedPort]) candidates[cand.relatedPort] = [];
candidates[cand.relatedPort].push(cand.port);
} else if (!e.candidate) {
if (Object.keys(candidates).length === 1) {
var ports = candidates[Object.keys(candidates)[0]];
console.log(ports.length === 1 ? 'normal nat' : 'symmetric nat');
}
}
};
pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
IP 数据包通过 NAT 设备(如路由器)时,NAT 会重写来源 IP 地址和目的地 IP 地址,从而实现同一内网中不同主机共用公网 IP 地址的功能。
如上图所示,192.168.100.3
的主机向 209.131.36.158
的 80 端口发送 HTTP 请求,经过 NAT 时,NAT 根据其保存的映射表,找到该主机对应的外网 IP 地址和端口,即 145.12.131.7:6282
,然后替换 IP 数据包的 Source 字段为该地址,并发送给 www.yahoo.com
。雅虎服务器发送 response 的时候,Dest 即为 145.12.131.7:6282
,然后 NAT 转发给 192.168.100.3
的 3855
端口。
但是,如果某个外网主机想主动访问 192.168.100.3
,显然不能到达内网的 192.168.100.3
主机,即使该主机知道了我们的公网 IP,它在主动建立连接时,NAT 发现并没有该地址和局域网内地址的映射,该数据包就会被 NAT 设备丢弃。
NAT 转换表的生成与 TCP/IP 报文的 IP 地址、端口号转换操作会产生一定的开销。
NAT 技术使用非常广泛,但也存在一些缺点:
- NAT 设备需要对收发的数据包重新编辑修改(IP 地址转换,重新计算校验),此操作会降低网络数据的传输速度
- NAT 设备端口老化问题,会导致连接中的设备异常断开,因为 NAT 设备需要维护端口映射表,而硬件资源有限,所以有些 NAT 设备会定时断开部分连接
- 部分网络协议无法通过 NAT 设备,让两台设备直接连接变得困难,因此,才出现了 NAT 打洞技术
NAT 打洞
NAT 打洞是一种技术,用于在两个位于不同私有网络中的设备之间建立直接通信。NAT 打洞通常使用 UDP 协议,所以也称为 UDP 打洞。 下面是 UDP 打洞的基本流程:
- 设备 A 和设备 B 都位于不同的私有网络中,它们都无法直接通信。
- 设备 A 向设备 B 发送一个 UDP 数据包,这个数据包包含了设备 A 的 IP 地址和端口号。
- 设备 B 收到这个数据包后,会记录下设备 A 的 IP 地址和端口号,并向设备A发送一个 UDP 数据包,这个数据包包含了设备 B 的 IP 地址和端口号。
- 设备 A 收到这个数据包后,会记录下设备 B 的 IP 地址和端口号。
- 现在,设备 A 和设备 B 都知道了对方的 IP 地址和端口号,它们可以直接通过这些信息进行 UDP 通信了。
1
2
3
4
5
6
7
8
9
10
11
12
Server S
18.181.0.31:5678
|
|
+----------------------+----------------------+
| |
NAT A NAT B
155.99.25.11:62000 138.76.29.7:31000
| |
| |
Client A Client B
192.168.0.100:1234 10.1.1.3:1234
那么问题来了,设备 A 向设备 B 发送一个 UDP 数据包,那么设备 A 如何知道设备 B 的 IP 地址和端口号呢?这就需要中介服务器了。 这个中介服务器可以是一个公网服务器或者是一个 STUN 服务器
。
TCP 可以打洞吗?UDP 打洞和 TCP 打洞的本质是一样的,都是通过在 NAT 设备上创建映射来实现两个位于不同 NAT 后面的主机之间的直接通信。但是,由于 UDP 协议的特点,UDP 打洞更加简单和高效。
STUN
STUN(Session Traversal Utilities for NAT)
是一种用于穿越网络地址转换(NAT)的协议,它通常用于 P2P 通信中的打洞操作。P2P 打洞旨在允许两个设备在 NAT 后进行直接通信,而不需要通过中间服务器。
Google 提供的 STUN 服务器,可以使用 webRTC 的 demo 测试下。
NAT 有不同的类型,不是所有 NAT 都支持 NAT 打洞,如前面提到的 Symmetric NAT
则无法打洞。UDP 打洞的成功率约为 60%,当打洞失败时,则使用基于 TURN
服务器的中继模式。
TURN
Traversal Using Relays around NAT (TURN)
旨在通过打开与 TURN 服务器的连接并通过该服务器中继所有信息来绕过对称 NAT 限制。您将创建与 TURN
服务器的连接,并告诉所有对等方将数据包发送到服务器,然后将其转发给您。这显然会带来一些开销,因此只有在没有其他选择的情况下才使用它。
经过 TURN
服务器中转的模式,通常称为 relay
。
参考资料
- https://en.wikipedia.org/wiki/Network_address_translation
- https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Protocols
- https://juejin.cn/post/6844904098572009485
- https://webrtchacks.com/symmetric-nat/
- https://www.volcengine.com/docs/6489/72015