乐趣区

物联网高并发编程之P2P技术NAT穿越方案

物联网高并发编程之 P2P 技术 NAT 穿越方案
更多物联网高并发编程知识请移步:https://www.yuque.com/shizhiy…
内容概述
P2P 即点对点通信,或称为对等联网,与传统的服务器客户端模式(如下图“P2P 结构模型”所示)有着明显的区别,在即时通讯方案中应用广泛(比如 IM 应用中的实时音视频通信、实时文件传输甚至文字聊天等)。
P2P 可以是一种通信模式、一种逻辑网络模型、一种技术、甚至一种理念。
在 P2P 网络中(如右图所示),所有通信节点的地位都是对等的,每个节点都扮演着客户机和服务器双重角色,节点之间通过直接通信实现文件信息、处理器运算能力、存储空间等资源的共享。
P2P 网络具有分散性、可扩展性、健壮性等特点,这使得 P2P 技术在信息共享、即时通讯、协同工作、分布式计算、网络存储等领域都有广阔的应用。1 经典的 CS 模式:
 P2P 结构模型:
NAT 技术和 P2P 技术作为经典的两项网络技术,在现在的网络上有着广泛的应用,P2P 主机位于 NAT 网关后面的情况屡见不鲜。NAT 技术虽然在一定程度上解决了 IPv4 地址短缺的问题,在构建防火墙、保证网络安全方面都发挥了一定的作用,却破坏了端到端的网络通信。NAT 阻碍主机进行 P2P 通信的主要原因是 NAT 不允许外网主机主动访问内网主机,但是 P2P 技术却要求通信双方都能主动发起访问,所以要在 NAT 网络环境中进行有效的 P2P 通信,就必须采用新的解决方案。
P2P 作为一项实用的技术,有很大的优化空间,并且相对于网络设备,基于 P2P 的应用程序在实现上更为灵活。所以为了兼容 NAT,基于 P2P 的应用程序在开发的时候大多会根据自身特点加入一些穿越 NAT 的功能以解决上述问题。以下着重介绍几种常见的 P2P 穿越 NAT 方案。**
反向链接技术
一种特殊的 P2P 场景(通信双方中只有一方位于 NAT 设备之后)
此种情况是所有 P2P 场景中最简单的,它使用一种被称为“反向链接技术”来解决这个问题。大致的原理如下所述。
如图所示,客户端 A 位于 NAT 之后,它通过 TCP 端口 1234 连接到服务器的 TCP 端口 1235 上,NAT 设备为这个连接重新分配了 TCP 端口 62000。客户端 B 也通过 TCP 端口 1234 连接到服务器端口 1235 上。A 和 B 从服务器处获知的对方的外网地址二元组 {IP 地址: 端口号} 分别为 {138.76.29.7:1234} 和{155.99.25.11:62000},它们在各自的本地端口上进行侦听。
由于 B 拥有外网 IP 地址,所以 A 要发起与 B 的通信,可以直接通过 TCP 连接到 B。但如果 B 尝试通过 TCP 连接到 A 进行 P2P 通信,则会失败,原因是 A 位于 NAT 设备后,虽然 B 发出的 TCP SYN 请求能够到达 NAT 设备的端口 62000,但 NAT 设备会拒绝这个连接请求。
要想与 Client A 通信,B 不是直接向 A 发起连接,而是通过服务器给 A 转发一个连接请求,反过来请求 A 连接到 B(即进行反向链接),A 在收到从服务器转发过来的请求以后,会主动向 B 发起一个 TCP 的连接请求,这样在 NAT 设备上就会建立起关于这个连接的相关表项,使 A 和 B 之间能够正常通信,从而建立起它们之间的 TCP 连接。

基于 UDP 协议的 P2P 打洞技术

原理概述
UDP 打洞技术是通过中间服务器的协助在各自的 NAT 网关上建立相关的表项,使 P2P 连接的双方发送的报文能够直接穿透对方的 NAT 网关,从而实现 P2P 客户端互连。如果两台位于 NAT 设备后面的 P2P 客户端希望在自己的 NAT 网关上打个洞,那么他们需要一个协助者——集中服务器,并且还需要一种用于打洞的 Session 建立机制。

什么是集中服务器?
集中服务器本质上是一台被设置在公网上的服务器,建立 P2P 的双方都可以直接访问到这台服务器。位于 NAT 网关后面的客户端 A 和 B 都可以与一台已知的集中服务器建立连接,并通过这台集中服务器了解对方的信息并中转各自的信息。同时集中服务器的另一个重要作用在于判断某个客户端是否在 NAT 网关之后。
具体的方法是:一个客户端在集中服务器上登陆的时候,服务器记录下该客户端的两对地址二元组信息{IP 地址:UDP 端口},一对是该客户端与集中服务器进行通信的自身的 IP 地址和端口号,另一对是集中服务器记录下的由服务器“观察”到的该客户端实际与自己通信所使用的 IP 地址和端口号。我们可以把前一对地址二元组看作是客户端的内网 IP 地址和端口号,把后一对地址二元组看作是客户端的内网 IP 地址和端口号经过 NAT 转换后的外网 IP 地址和端口号。集中服务器可以从客户端的登陆消息中得到该客户端的内网相关信息,还可以通过登陆消息的 IP 头和 UDP 头得到该客户端的外网相关信息。如果该客户端不是位于 NAT 设备后面,那么采用上述方法得到的两对地址二元组信息是完全相同的。P2P 的 Session 建立原理:假定客户端 A 要发起对客户端 B 的直接连接,具体的“打洞”过程如下:

1)A 最初不知道如何向客户端 B 发起连接,于是 A 向集中服务器发送消息,请求集中服务器帮助建立与客户端 B 的 UDP 连接。
2)集中服务器将含有 B 的外网和内网的地址二元组发给 A,同时,集中服务器将包含有 A 的外网和内网的地址二元组信息的消息也发给 B。这样一来,A 与 B 就都知道对方外网和内网的地址二元组信息了。
3)当 A 收到由集中服务器发来的包含 B 的外网和内网的地址二元组信息后,A 开始向 B 的地址二元组发送 UDP 数据包,并且 A 会自动锁定第一个给出响应的 B 的地址二元组。同理,当 B 收到由集中服务器发来的 A 的外网和内网地址二元组信息后,也会开始向 A 的外网和内网的地址二元组发送 UDP 数据包,并且自动锁定第一个得到 A 回应的地址二元组。由于 A 与 B 互相向对方发送 UDP 数据包的操作是异步的,所以 A 和 B 发送数据包的时间先后并没有时序要求。

下面来看下这三者之间是如何进行 UDP 打洞的。在这我们分三种具体情景来讨论:

第一种是最简单的一种情景,两个客户端都位于同一个 NAT 设备后面,即位于同一内网中;
第二种是最普遍的一种情景,两个客户端分别位于不同的 NAT 设备后面,分属不同的内网;
第三种是客户端位于两层 NAT 设备之后,通常最上层的 NAT 是由网络提供商提供的,第二层 NAT 是家用的 NAT 路由器之类的设备提供的。

典型 P2P 情景 1:两客户端位于同一 NAT 设备后面
这是最简单的一种情况(如图 4 所示):客户端 A 和 B 分别与集中服务器建立 UDP 连接,经过 NAT 转换后,A 的公网端口被映射为 62000,B 的公网端口映射为 62005。位于同一个 NAT 设备后的 UDP 打洞过程:
当 A 向集中服务器发出消息请求与 B 进行连接,集中服务器将 B 的外网地址二元组以及内网地址二元组发给 A,同时把 A 的外网以及内网的地址二元组信息发给 B。A 和 B 发往对方公网地址二元组信息的 UDP 数据包不一定会被对方收到,这取决于当前的 NAT 设备是否支持不同端口之间的 UDP 数据包能否到达(即 Hairpin 转换特性),无论如何 A 与 B 发往对方内网的地址二元组信息的 UDP 数据包是一定可以到达的,内网数据包不需要路由,且速度更快。A 与 B 推荐采用内网的地址二元组信息进行常规的 P2P 通信。
假定 NAT 设备支持 Hairpin 转换,P2P 双方也应忽略与内网地址二元组的连接,如果 A 和 B 采用外网的地址二元组做为 P2P 通信的连接,这势必会造成数据包无谓地经过 NAT 设备,这是一种对资源的浪费。就目前的网络情况而言,应用程序在“打洞”的时候,最好还是把外网和内网的地址二元组都尝试一下。如果都能成功,优先以内网地址进行连接。
什么是 Hairpin 技术?
Hairpin 技术又被称为 Hairpin NAT、Loopback NAT 或 Hairpin Translation。Hairpin 技术需要 NAT 网关支持,它能够让两台位于同一台 NAT 网关后面的主机,通过对方的公网地址和端口相互访问,NAT 网关会根据一系列规则,将对内部主机发往其 NAT 公网 IP 地址的报文进行转换,并从私网接口发送给目标主机。目前有很多 NAT 设备不支持该技术,这种情况下,NAT 网关在一些特定场合下将会阻断 P2P 穿越 NAT 的行为,打洞的尝试是无法成功的。好在现在已经有越来越多的 NAT 设备商开始加入到对该转换的支持中来。

典型 P2P 情景 2:两客户端位于不同的 NAT 设备后面
这是最普遍的一种情况(如图 5 所示):客户端 A 与 B 经由各自的 NAT 设备与集中服务器建立 UDP 连接,A 与 B 的本地端口号均为 4321,集中服务器的公网端口号为 1234。在向外的会话中,A 的外网 IP 被映射为 155.99.25.11,外网端口为 62000;B 的外网 IP 被映射为 138.76.29.7,外网端口为 31000。如下所示:**
客户端 A——> 本地 IP:10.0.0.1,本地端口:4321,外网 IP:155.99.25.11,外网端口:62000
客户端 B——> 本地 IP:10.1.1.3,本地端口:4321,外网 IP:138.76.29.7,外网端口:31000
位于不同 NAT 设备后的 UDP 打洞过程:
在 A 向服务器发送的登陆消息中,包含有 A 的内网地址二元组信息,即 10.0.0.1:4321;服务器会记录下 A 的内网地址二元组信息,同时会把自己观察到的 A 的外网地址二元组信息记录下来。同理,服务器也会记录下 B 的内网地址二元组信息和由服务器观察到的客户端 B 的外网地址二元组信息。无论 A 与 B 二者中的任何一方向服务器发送 P2P 连接请求,服务器都会将其记录下来的上述的外网和内网地址二元组发送给 A 或 B。
A 和 B 分属不同的内网,它们的内网地址在外网中是没有路由的,所以发往各自内网地址的 UDP 数据包会发送到错误的主机或者根本不存在的主机上。当 A 的第一个消息发往 B 的外网地址(如图 3 所示),该消息途经 A 的 NAT 设备,并在该设备上生成一个会话表项,该会话的源地址二元组信息是 {10.0.0.1:4321},和 A 与服务器建立连接的时候 NAT 生成的源地址二元组信息一样,但它的目的地址是 B 的外网地址。在 A 的 NAT 设备支持保留 A 的内网地址二元组信息的情况下,所有来自 A 的源地址二元组信息为{10.0.0.1:4321} 的数据包都沿用 A 与集中服务器事先建立起来的会话,这些数据包的外网地址二元组信息均被映射为{155.99.25.11:62000}。
A 向 B 的外网地址发送消息的过程就是“打洞”的过程,从 A 的内网的角度来看应为从 {10.0.0.1:4321} 发往 {138.76.29.7:31000},从 A 在其 NAT 设备上建立的会话来看,是从{155.99.25.11:62000} 发到{138.76.29.7:31000}。如果 A 发给 B 的外网地址二元组的消息包在 B 向 A 发送消息包之前到达 B 的 NAT 设备,B 的 NAT 设备会认为 A 发过来的消息是未经授权的外网消息,并丢弃该数据包。
B 发往 A 的消息包也会在 B 的 NAT 设备上建立一个 {10.1.1.3:4321,155.99.25.11:62000} 的会话(通常也会沿用 B 与集中服务器连接时建立的会话,只是该会话现在不仅接受由服务器发给 B 的消息,还可以接受从 A 的 NAT 设备 {155.99.25.11:6200} 发来的消息)。
一旦 A 与 B 都向对方的 NAT 设备在外网上的地址二元组发送了数据包,就打开了 A 与 B 之间的“洞”,A 与 B 向对方的外网地址发送数据,等效为向对方的客户端直接发送 UDP 数据包了。一旦应用程序确认已经可以通过往对方的外网地址发送数据包的方式让数据包到达 NAT 后面的目的应用程序,程序会自动停止继续发送用于“打洞”的数据包,转而开始真正的 P2P 数据传输。

典型 P2P 情景 3:两客户端位于两层(或多层)NAT 设备之后
此种情景最典型的部署情况就像这样:最上层的 NAT 设备通常是由网络提供商(ISP)提供,下层 NAT 设备是家用路由器。
如图所示:假定 NAT C 是由 ISP 提供的 NAT 设备,NAT C 提供将多个用户节点映射到有限的几个公网 IP 的服务,NAT A 和 NAT B 作为 NAT C 的内网节点将把用户的内部网络接入 NAT C 的内网,用户的内部网络就可以经由 NAT C 访问公网了。从这种拓扑结构上来看,只有服务器与 NAT C 是真正拥有公网可路由 IP 地址的设备,而 NAT A 和 NAT B 所使用的公网 IP 地址,实际上是由 ISP 服务提供商设定的(相对于 NAT C 而言)内网地址(我们将这种由 ISP 提供的内网地址称之为“伪”公网地址)。同理,隶属于 NAT A 与 NAT B 的客户端,它们处于 NAT A,NAT B 的内网,以此类推,客户端可以放到到多层 NAT 设备后面。客户端 A 和客户端 B 发起对服务器 S 的连接的时候,就会依次在 NAT A 和 NAT B 上建立向外的 Session,而 NAT A、NAT B 要联入公网的时候,会在 NAT C 上再建立向外的 Session。现在假定客户端 A 和 B 希望通过 UDP“打洞”完成两个客户端的 P2P 直连。最优化的路由策略是客户端 A 向客户端 B 的“伪公网”IP 上发送数据包,即 ISP 服务提供商指定的内网 IP,NAT B 的“伪”公网地址二元组,{10.0.1.2:55000}。由于从服务器的角度只能观察到真正的公网地址,也就是 NAT A,NAT B 在 NAT C 建立 session 的真正的公网地址 {155.99.25.11:62000} 以及{155.99.25.11:62005},非常不幸的是客户端 A 与客户端 B 是无法通过服务器知道这些“伪”公网的地址,而且即使客户端 A 和 B 通过某种手段可以得到 NAT A 和 NAT B 的“伪”公网地址,我们仍然不建议采用上述的“最优化”的打洞方式,这是因为这些地址是由 ISP 服务提供商提供的或许会存在与客户端本身所在的内网地址重复的可能性(例如:NAT A 的内网的 IP 地址域恰好与 NAT A 在 NAT C 的“伪”公网 IP 地址域重复,这样就会导致打洞数据包无法发出的问题)。
因此客户端别无选择,只能使用由公网服务器观察到的 A,B 的公网地址二元组进行“打洞”操作,用于“打洞”的数据包将由 NAT C 进行转发。
当客户端 A 向客户端 B 的公网地址二元组 {155.99.25.11:62005} 发送 UDP 数据包的时候,NAT A 首先把数据包的源地址二元组由 A 的内网地址二元组 {10.0.0.1:4321} 转换为“伪”公网地址二元组{10.0.1.1:45000},现在数据包到了 NAT C,NAT C 应该可以识别出来该数据包是要发往自身转换过的公网地址二元组,如果 NAT C 可以给出“合理”响应的话,NAT C 将把该数据包的源地址二元组改为{155.99.25.11:62000},目的地址二元组改为{10.0.1.2:55000},即 NAT B 的“伪”公网地址二元组,NAT B 最后会将收到的数据包发往客户端 B。同样,由 B 发往 A 的数据包也会经过类似的过程。目前也有很多 NAT 设备不支持类似这样的“Hairpin 转换”,但是已经有越来越多的 NAT 设备商开始加入对该转换的支持中来。
一个需要考虑的现实问题:UDP 在空闲状态下的超时
当然,从应用的角度上来说,在完成打洞过程的同时,还有一些技术问题需要解决,如 UDP 在空闲状态下的超时问题。由于 UDP 转换协议提供的“洞”不是绝对可靠的,多数 NAT 设备内部都有一个 UDP 转换的空闲状态计时器,如果在一段时间内没有 UDP 数据通信,NAT 设备会关掉由“打洞”过程打出来的“洞”。如果 P2P 应用程序希望“洞”的存活时间不受 NAT 网关的限制,就最好在穿越 NAT 以后设定一个穿越的有效期。
对于有效期目前没有标准值,它与 NAT 设备内部的配置有关,某些设备上最短的只有 20 秒左右。在这个有效期内,即使没有 P2P 数据包需要传输,应用程序为了维持该“洞”可以正常工作,也必须向对方发送“打洞”心跳包。这个心跳包是需要双方应用程序都发送的,只有一方发送不会维持另一方的 Session 正常工作。除了频繁发送“打洞”心跳包以外,还有一个方法就是在当前的“洞”超时之前,P2P 客户端双方重新“打洞”,丢弃原有的“洞”,这也不失为一个有效的方法。
基于 TCP 协议的 P2P 打洞技术详细
建立穿越 NAT 设备的 P2P 的 TCP 连接只比 UDP 复杂一点点,TCP 协议的”“打洞”从协议层来看是与 UDP 的“打洞”过程非常相似的。尽管如此,基于 TCP 协议的打洞至今为止还没有被很好的理解,这也造成了的对其提供支持的 NAT 设备不是很多。在 NAT 设备支持的前提下,基于 TCP 的“打洞”技术实际上与基于 UDP 的“打洞”技术一样快捷、可靠。实际上,只要 NAT 设备支持的话,基于 TCP 的 P2P 技术的健壮性将比基于 UDP 技术的更强一些,因为 TCP 协议的状态机给出了一种标准的方法来精确的获取某个 TCP session 的生命期,而 UDP 协议则无法做到这一点。**
套接字和 TCP 端口的重用
实现基于 TCP 协议的 P2P 打洞过程中,最主要的问题不是来自于 TCP 协议,而是来自于应用程序的 API 接口。这是由于标准的伯克利 (Berkeley) 套接字的 API 是围绕着构建客户端 / 服务器程序而设计的,API 允许 TCP 流套接字通过调用 connect()函数来建立向外的连接,或者通过 listen()和 accept 函数接受来自外部的连接,但是,API 不提供类似 UDP 那样的,同一个端口既可以向外连接,又能够接受来自外部的连接。而且更糟的是,TCP 的套接字通常仅允许建立 1 对 1 的响应,即应用程序在将一个套接字绑定到本地的一个端口以后,任何试图将第二个套接字绑定到该端口的操作都会失败。
为了让 TCP“打洞”能够顺利工作,我们需要使用一个本地的 TCP 端口来监听来自外部的 TCP 连接,同时建立多个向外的 TCP 连接。幸运的是,所有的主流操作系统都能够支持特殊的 TCP 套接字参数,通常叫做“SO_REUSEADDR”,该参数允许应用程序将多个套接字绑定到本地的一个地址二元组(只要所有要绑定的套接字都设置了 SO_REUSEADDR 参数即可)。BSD 系统引入了 SO_REUSEPORT 参数,该参数用于区分端口重用还是地址重用,在这样的系统里面,上述所有的参数必须都设置才行。
打开 P2P 的 TCP 流
假定客户端 A 希望建立与 B 的 TCP 连接。我们像通常一样假定 A 和 B 已经与公网上的已知服务器建立了 TCP 连接。服务器记录下来每个接入的客户端的公网和内网的地址二元组,如同为 UDP 服务的时候一样。从协议层来看,TCP“打洞”与 UDP“打洞”是几乎完全相同的过程:

客户端 A 使用其与服务器的连接向服务器发送请求,要求服务器协助其连接客户端 B;
服务器将 B 的公网和内网的 TCP 地址的二元组信息返回给 A,同时,服务器将 A 的公网和内网的地址二元组也发送给 B;
客户端 A 和 B 使用连接服务器的端口异步地发起向对方的公网、内网地址二元组的 TCP 连接,同时监听各自的本地 TCP 端口是否有外部的连接联入;
A 和 B 开始等待向外的连接是否成功,检查是否有新连接联入。如果向外的连接由于某种网络错误而失败,如:“连接被重置”或者“节点无法访问”,客户端只需要延迟一小段时间(例如延迟一秒钟),然后重新发起连接即可,延迟的时间和重复连接的次数可以由应用程序编写者来确定;
TCP 连接建立起来以后,客户端之间应该开始鉴权操作,确保目前联入的连接就是所希望的连接。如果鉴权失败,客户端将关闭连接,并且继续等待新的连接联入。客户端通常采用“先入为主”的策略,只接受第一个通过鉴权操作的客户端,然后将进入 P2P 通信过程不再继续等待是否有新的连接联入。

TCP 打洞:
与 UDP 不同的是,因为使用 UDP 协议的每个客户端只需要一个套接字即可完成与服务器的通信,而 TCP 客户端必须处理多个套接字绑定到同一个本地 TCP 端口的问题,如图 7 所示。现在来看实际中常见的一种情景,A 与 B 分别位于不同的 NAT 设备后面,如图 5 所示,并且假定图中的端口号是 TCP 协议的端口号,而不是 UDP 的端口号。图中向外的连接代表 A 和 B 向对方的内网地址二元组发起的连接,这些连接或许会失败或者无法连接到对方。如同使用 UDP 协议进行“打洞”操作遇到的问题一样,TCP 的“打洞”操作也会遇到内网的 IP 与“伪”公网 IP 重复造成连接失败或者错误连接之类的问题。
客户端向彼此公网地址二元组发起连接的操作,会使得各自的 NAT 设备打开新的“洞”允许 A 与 B 的 TCP 数据通过。如果 NAT 设备支持 TCP“打洞”操作的话,一个在客户端之间的基于 TCP 协议的流通道就会自动建立起来。如果 A 向 B 发送的第一个 SYN 包发到了 B 的 NAT 设备,而 B 在此前没有向 A 发送 SYN 包,B 的 NAT 设备会丢弃这个包,这会引起 A 的“连接失败”或“无法连接”问题。而此时,由于 A 已经向 B 发送过 SYN 包,B 发往 A 的 SYN 包将被看作是由 A 发往 B 的包的回应的一部分,所以 B 发往 A 的 SYN 包会顺利地通过 A 的 NAT 设备,到达 A,从而建立起 A 与 B 的 P2P 连接。
从应用程序的角度来看 TCP“打洞”
从应用程序的角度来看,在进行 TCP“打洞”的时候都发生了什么呢?假定 A 首先向 B 发出 SYN 包,该包发往 B 的公网地址二元组,并且被 B 的 NAT 设备丢弃,但是 B 发往 A 的公网地址二元组的 SYN 包则通过 A 的 NAT 到达了 A,然后,会发生以下的两种结果中的一种,具体是哪一种取决于操作系统对 TCP 协议的实现:
(1)A 的 TCP 实现会发现收到的 SYN 包就是其发起连接并希望联入的 B 的 SYN 包,通俗一点来说就是“说曹操,曹操到”的意思,本来 A 要去找 B,结果 B 自己找上门来了。A 的 TCP 协议栈因此会把 B 作为 A 向 B 发起连接 connect 的一部分,并认为连接已经成功。程序 A 调用的异步 connect()函数将成功返回,A 的 listen()等待从外部联入的函数将没有任何反映。此时,B 联入 A 的操作在 A 程序的内部被理解为 A 联入 B 连接成功,并且 A 开始使用这个连接与 B 开始 P2P 通信。
由于收到的 SYN 包中不包含 A 需要的 ACK 数据,因此,A 的 TCP 将用 SYN-ACK 包回应 B 的公网地址二元组,并且将使用先前 A 发向 B 的 SYN 包一样的序列号。一旦 B 的 TCP 收到由 A 发来的 SYN-ACK 包,则把自己的 ACK 包发给 A,然后两端建立起 TCP 连接。简单的说,第一种,就是即使 A 发往 B 的 SYN 包被 B 的 NAT 丢弃了,但是由于 B 发往 A 的包到达了 A。结果是,A 认为自己连接成功了,B 也认为自己连接成功了,不管是谁成功了,总之连接是已经建立起来了。
(2)另外一种结果是,A 的 TCP 实现没有像(1)中所讲的那么“智能”,它没有发现现在联入的 B 就是自己希望联入的。就好比在机场接人,明明遇到了自己想要接的人却不认识,误认为是其他的人,安排别人给接走了,后来才知道是自己错过了机会,但是无论如何,人已经接到了任务已经完成了。然后,A 通过常规的 listen()函数和 accept()函数得到与 B 的连接,而由 A 发起的向 B 的公网地址二元组的连接会以失败告终。尽管 A 向 B 的连接失败,A 仍然得到了 B 发起的向 A 的连接,等效于 A 与 B 之间已经联通,不管中间过程如何,A 与 B 已经连接起来了,结果是 A 和 B 的基于 TCP 协议的 P2P 连接已经建立起来了。
第一种结果适用于基于 BSD 的操作系统对于 TCP 的实现,而第二种结果更加普遍一些,多数 Linux 和 Windows 系统都会按照第二种结果来处理。

总结
在 IP 地址极度短缺的今天,NAT 几乎已经是无所不在的一项技术了,以至于现在任何一项新技术都不得不考虑和 NAT 的兼容。作为当下应用最广泛的技术之一,P2P 技术也必然要面对 NAT 这个障碍。
打洞技术看起来是一项近似乎蛮干的技术,却不失为一种有效的技术手段。在集中服务器的帮助下,P2P 的双方利用端口预测的技术在 NAT 网关上打出通道,从而实现 NAT 穿越,解决了 NAT 对于 P2P 的阻隔,为 P2P 技术在网络中更广泛的推广作出了非常大的贡献。

退出移动版