假设:一台公网服务器S,一台内网主机A及其路由器NAT-A,另一台内网主机B及其路由器NAT-B,借助S将A和B建立直接的TCP连接,即由B向A打一个洞,让A可以沿这个洞直接连接到B主机,就好像NAT-B不存在一样。
实现过程如下:
1、 S启动两个网络侦听,一个叫【主连接】侦听,一个叫【协助打洞】的侦听。
2、 A和B分别与S的【主连接】保持联系。
3、 当A需要和B建立直接的TCP连接时,首先连接S的【协助打洞】端口,并发送协助连接申请。同时在该端口号上启动侦听。注意由于要在相同的网络终端上绑定到不同的套接字上,所以必须为这些套接字设置 SO_REUSEADDR 属性(即允许重用),否则侦听会失败。
4、 S的【协助打洞】连接收到A的申请后通过【主连接】通知B,并将A经过NAT-A转换后的公网IP地址和端口等信息告诉B。
5、 B收到S的连接通知后首先与S的【协助打洞】端口连接,随便发送一些数据后立即断开,这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。
6、 B尝试与A的经过NAT-A转换后的公网IP地址和端口进行connect,根据不同的路由器会有不同的结果,有些路由器在这个操作就能建立连接(例如我用的TPLink R402),大多数路由器对于不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-A会纪录此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即B向A打了一个洞,下次A就能直接连接到B刚才使用的端口号了。
7、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪以后通过与S的【主连接】回复消息“我已经准备好”,S在收到以后将B经过NAT-B转换后的公网IP和端口号告诉给A。
8、 A收到S回复的B的公网IP和端口号等信息以后,开始连接到B公网IP和端口号,由于在步骤6中B曾经尝试连接过A的公网IP地址和端口,NAT-A纪录了此次连接的信息,所以当A主动连接B时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的TCP连接建立起来了。
手机跟PC机TCP打洞,PC机充当服务端,手机充当客户端,公网服务器Server充当桥梁。
备注:在建立PC和手机直连之前,Server充当服务器,P2P建立后,可以关闭撤掉
注意:由于要在相同的网络终端上绑定到不同的Socket上,所以必须将Socket选项设置为 SO_REUSEADDR 属性(即允许重用)
1、Server启动2个Socket网络监听,一个主连接Socket,一个协助打洞Socket,PC和手机分别与主连接Socket建立联系。
2、手机连接Server的协助打洞Socket,并发送协助连接申请,并启动监听接受数据。
3、Server的协助打洞Socket收到手机的申请,通过已存在的主连接Socket通知PC,并将手机的公网IP和端口告诉PC。
4、PC收到Server的连接通知后,先与协助打洞Socket连接,发送一次随意数据立即断开,让Server获取到PC的外网IP和端口。
5、PC尝试连接手机的公网IP和端口,根据不同的路由器会有不同的结果,大多数会connect失败,但是会记住此次连接的源地址和端口号,为接下来真正的连接做好了准备,这就是所谓的打洞,即PC向手机打了一个洞,下次手机就能直接连接到PC刚才使用的端口号。
6、PC打洞的同时,在相同的端口上启动侦听,并给主连接Socket发送“我已准备好了”,Server在收到后,把PC的公网IP和端口告诉手机。
7、手机收到后,就可以直连到PC。