子网内的计算机向外连结是很容易的, 但是如果外部的计算机想访问子网内的计算机就比较困难了(而这正是P2P所需要的 那么我们如果想从外部发送一个数据包给居于内网的计算机有什么办法呢?首先,我们必须在此内网的NAT上打上一个”洞”(也就是前面我们说的在NAT上建立一个 Session),这个洞不能由外部来打,只能由此内网内的被访问主机来打, 而且这个洞是有方向的,比如从内部某台主机(比如:192.168.0.10)向外部的某个IP(比如:219.237.60.1)发送一个UDP包,那么就在这个内网的NAT设备上打了一个通往219.237.60.1的”洞”(这就是称为UDP Hole Punching的技术),以后219.237.60.1就可以通过这个洞与内网的192.168.0.10联系了。(但是其它的IP不能利用这个洞)。 有了上面的理论,实现两个内网的主机通讯就差最后一步了:那就是鸡生蛋还是蛋生鸡的问题了,两边都无法主动发出连接请求,谁也不知道谁的公网地址,那我们如何来打这个洞呢?我们需要一个中间人来联系这两个内网主机。 现在我们来看看一个P2P软件的流程,以下图为例: Server S (219.237.60.1) +———————-+———————————-+————————+ NAT A (外网IP:202.187.45.3) NAT B (外网IP:187.34.1.56) (内网IP:192.168.0.1) (内网IP:192.168.0.1) port=60000 port=40000 Client A (192.168.0.20:xxxx) Client B (192.168.0.10:yyyy) 首先,Client A登录服务器,NAT A为这次的Session分配了一个端口60000,那么Server S 收到的Client A的地址是202.187.45.3:60000,这就是Client A的外网地址了。 同样,Client B登录Server S,NAT B给此次Session分配的端口是40000,那么Server S 收到的B的地址是187.34.1.56:40000。 此时,Client A与Client B都可以与Server S通信了。如果Client A此时想直接发送信息给Client B,那么它可以从Server S那儿获得B(如何从服务器S中找到目标主机B呢?依靠事先获得的注册信息,or通过识别主机B的某种特征,如能力或资源名?)的公网地址187.34.1.56:40000,是不是Client A向这个地址发送信息Client B就能收到了呢?答案是不行,因为如果这样发送信息,NAT B会将这个信息丢弃(因为这样的信息是不请自来的,为了安全,大多数NAT都会执行丢弃动作)。现在我们需要的是在NAT B上打一个通向对断NAT 202.187.45.3(即Client A的外网地址)的洞,那么Client A发送到187.34.1.56:40000的信息就能被Client B收到了。这个打洞命令由谁来发呢? 过程如下: 概念:私有地址/端口和公有地址/端口 我们知道,现在大部分网络采用的都是 NAPT(Network Address/Port Translator)了,这个东东的作用是一个对外的会话在经过NAT之后,其IP地址和端口号都会被改写。在这里,我们把一次会话中客户自己认为在使用的IP地址和端口号称为私有地址/端口,而把经过NAPT之后被改写的IP地址和端口号称为公有地址/端口。 客户端首先得到自己的私有地址/端口,然后向server端发送登录请求,server端在得到这个请求之后就可以知道这个client端的公有地址/端口,server会为每一个登录的client保存它们的私有地址/端口和公有地址/端口。 OK,下面开始关键的打洞流程。假设client A 要向client B 对话,但是A不知道B的地址,即使知道,根据NAT的原理,这个对话在第一次会被拒绝,因为client B 的NAT认为这是一个从没有过的外部发来的请求。这个时候,A如果发现自己没有保存B的地址,或者说发送给B的会话请求失败了,它会要求server端通知B向A打一个洞,这个B->A的会话意义在于它使NAT B 认为A的地址/端口是可以通过(即已被认可)的地址/端口,这样A再向B发送对话的时候就不会再被NAT B拒绝了。打一个比方来说明打洞的过程:A想来B家做客,但是遭到了B的管家NAT B的拒绝,理由是”我从来没有听我家B提过你的名字”,这时A找到了A和B都认识的朋友server,要求server给B报一个信,让B去跟管家说A是我的朋友。于是,B跟管家NAT B说,A是我认识的朋友,这样A的访问请求就不会再被管家NAT B所拒绝了。简而言之,UDP打洞就是一个通过server保存下来的地址使得彼此之间能够直接通信的过程,server只管帮助建立连接,在建立间接之后就不再介入了。 总结一下这个过程:如果Client A想向Client B发送信息,那么Client A发送命令给Server S,请求Server S 命令Client B 向Client A 方向打洞。呵呵,是不是很绕口,不过没关系,想一想就很清楚了,何况还有源代码呢(侯老师说过:在源代码面前没有秘密 !)),然后Client A就可以通过Client B的外网地址与Client B通信了。 注意:以上过程只适合于Cone NAT的情况,如果是Symmetric NAT,那么当Client B 向Client A 打洞的端口已经重新分配了,Client B 将无法知道这个端口(如果Symmetric NAT的端口是顺序分配的,那么我们或许可以猜测这个端口号,可是由于可能导致失败的因素太多,我们不推荐这种猜测端口的方法)。
本文目录
本文目录