基于P2P协议通信模式的选型(一)
基本上,使用.NET进行网络通信已经非常简单了,丰富强大的网络编程类库的支持,让一个初学者可以照着几个简短的SampleCode,鼓捣几个诸如Socket、TcpListener类型的对象,加上open、send、accept、close这么几个方法,不用半天的功夫就能翻出个新版来。然而更多的新手程序员面临的问题是,拿着这些实验室里产出的P2P通讯模块,一拿到真实的应用中,就完全哑火了。造成这种情况的最根本原因,就是因为那些实验室代码的调试环境是在一个理想的网段内的,而实际的Internet(甚至一些企业的Intranet)中,NATs、Firewalls无处不在,这些设备(有名为“middleboxes”)使得位于其后方的终端共享同一个公共IP,以此来节约网络资源,同时建立一个不对称的连接与寻址模式:即位于middleboxes之后的终端可以轻松地连接处于公网上的地址(资源),但是位于公网上的终端却很难访问到middleboxes之后的终端。除非是管理员做了相应的设置,如为NAT配置端口映射,否则发往middleboxes身后终端的数据包会被middleboxes丢弃掉。
矛盾点在于,一个实际的应用,往往要面对的,就是这样一个复杂地网络环境,比如说一个P2P的聊天平台,在Internet应用中,聊友A跟聊友B几乎不可能是同处在同一个内网环境下的,反倒更有可能的是A与B都是分别处于不同的middleboxes之后。更何况,即使是在单纯的网络环境下,现在也几乎没有人的PC不开防毒软件跟防火墙地裸奔的,否则基本后果很严重。基于A与B所处的不同环境,针对一些常见的相关应用,文章罗列了一下具体的实现方法与操作原理。
1、中继传输(Relaying)
鉴于NAT与Firewall在不做明确特殊性配置的时候,是会无条件拒绝任何TCP连接的,因此,对于TCP连接的连台主机,基本采用的方式,是在公共网络暴露一个服务端S,A与B想要进行相互的通信,虽然middleboxes阻止他们相互找到对方,但是A与B却都是可以主动连接到公共网络上的S,这样,在S上开端口监听A、B的请求,建立Tcp连接之后,将AB需要互换的信息通过已经建立起来并被维护的两条TCP连接进行传输。这是最可靠,但是资源开销最大的方式,Tcp的特性决定了在建立连接和发送数据的时候效率比较低,并不适用于一些例如语音、视频等实时信息的传送。当然,这种模式在UDP协议上也是可以应用的,但是共同的弊端是,一旦并发访问量大的时候,S将会面临巨大的转发压力,网络已经运算性能都将难以支撑。
显而易见,廉价的方式是,暴露一个服务器S,S来协助A,B建立一个连接,然后让AB对等地在建立的连接上交换数据包,并与S无关。这也是P2P的初衷所在。
2、逆向连接(Connection reversal)
当A和B其中一个是暴露在公网上的,而另外一个处于middleboxes之后,假定A在middleboxes之后,B暴露,也即是当A知道了B的IP地址和端口之后,就肯定能够连接到B的,而B即使获取到了A所在middlebox的IP与端口,也依旧连不上A,因为从B发出来的IP包其实指向的是middlebox,并且将会被middlebox丢弃。这时候A与B想要进行通信,那么我们可以使用逆向连接的方式。逆向连接的方式依然需要一个暴露的且地址端口共知的服务器S,B定时地向S发送报文,报告自己的位置,以及是否要联络A,S记录B的位置以及请求状态。当A处于被动监听的时候,A定时地向S发送报文,询问S有没有人找它,如果发现有B在找它,则从S处取B上报的位置(IP、端口),然后断开S,向B发起连接,并在建立的连接上进行数据交换;当A处于主动想要找B的时候,就更简单了,只需要直接连接S获取到B的位置,然后同样断开S,与B建立对等地连接进行通信。
3、UDP hole punching
这是重点了,因为这个是解决我们所真正面对的问题的开始,即两个终端都处在middleboxes之后,但是应用实时性需求比较高的时候。这里还需要细分到三种情况下面来讨论:位于同一个middlebox之后,还是不同的middlebox之后,甚至于位于多重middleboxes之后。
3.1 终端处于不同的NAT之后Peers behind different NATs
开始之前,我想再提醒大家注意一下这种方法的名称——是的,是UDP哦。假设一下,终端A在NAT-A(假定middleboxes为cone NAT,因为UDP hole punching是借助cone NAT的特性的)之后,终端B在NAT-B之后,同样建立一个暴露且共知的服务器S。我们让AB各自向S发送UDP数据包,汇报自己的位置,其实S收到的,是AB相对应的NAT-A和NAT-B的位置。此时NAT-A与NAT-B分别分配给他们身后的AB以固定的端口号,基于cone NAT的特性,这两个端口号是可以接受外部的数据并转发到AB上的,但是发送源就限定了必须是S,此时S向AB回复他们对方的位置——其实是NAT-A、NAT-B的IP地址,以及他们分别用来跟S交互的端口,后面就简单描述为A、B的位置了。第一步完成了AB位置的相互通知,并且A与S,B与S现在是能够相互发送UDP数据包了。A先向S发送一个请求,让S给B发送一个UDP数据,A向S请求完之后,就开始不停地向B发送数据(前期肯定会被挡掉很多,但是坚持),B收到S发过来的请求之后,立刻向A不停的发数据。这样一来,NAT-A的端口向B发数据,也只接受B发过来的数据,NAT-B的端口向A发数据也只接受A的数据,就此,一个逻辑连接就建立了!
一旦位于middlebox后面的A与B建立了一个直接的P2P连接,我们可以类推出A和B就都可以担当S,继续和其它的终端建立连接。当然,若AB中的任意一个或两个不在middlebox后面时候,UDP hole punching同样可以建立P2P通讯通道,不需要判断middlebox的类型,在变动的环境下有很好的兼容性,尤其当终端处于多重NAT后面的时候,Hole punching的能力尤为突出。
3.2终端处于相同的NAT之后Peers behind the same NATs
在相同NAT之后的情况要简单的多,使用上述的方法也一样可以实现,问题是,上述的方法,A与B建立了P2P通道之后,他们相互发送的每一包数据,都是经过了NAT-A与NAT-B进行转发的,白白浪费了资源,因为在相同的NAT下面的终端是可以直接通信的。我们让A跟B先各自向S发送UDP数据包,包里包含他们在子网下的IP地址和端口,S服务器判断需要通信的两个对象AB的外部地址(NAT)来自于同一个,则分别向AB发送对方内部地址,AB收到之后,使用对方的内部地址进行UDP发报,建立P2P通信通道。这样AB的通信就在内网中进行了,与NAT与S都无关。
3.3 终端处于多级NAT之后(Peers separated by multiple NATs)
以电信这样的ISP为例,它存在一个巨型的NAT,然后再分到各个省市区到用户的家里,一层一层。处于多级的NAT之后的AB需要使用UDP进行通信的时候,同样首先暴露一个共知服务器S,我们所期望的是如同3.1那样,AB发送到S交换位置信息,最终能够A向B发送UDP包的同时B也向A发送UDP数据,以此建立连接。事实上,若S是拥有世界最高级别IP地址(非NAT再分配的),当AB同时请求它的时候,他见到的很可能是相同的IP地址——电信的最高层的NAT地址。并且NAT内部地址的分配会因为用户的设定而发生重复,因此在这种状况下,往往只能依赖电信最高层的NAT来进行loopback translation,使用S服务器能够抓的到的外部IP进行数据交互。
最后,还是要再次提醒,UDP hole punching技术是基于UDP的,依赖于cone NAT特性的一种技术。也就是说终端之上的NAT必须都是cone NAT才可以,因为在cone NAT下,只要UDP还在使用,NAT就会保持住外网IP+端口与内网主机IP+端口的绑定,这恰恰是之前UDP通信通道可以建立的基础。Cone NAT的应用是非常普遍的,因此,UDP hole punching技术的应用也同样的普遍,但是在非cone NAT配置下,如对等NAT的时候,这种技术就会失效不可用。
最近服务系统升级,动不动就是在机房一整天,实在是太累,太困了,明天继续….

浙公网安备 33010602011771号