关于TCP打洞问题
沈小池 2016-09-19 11:08:06 【项目概况】:实现类似微信的功能:搭建Java服务器,客户端为Android开发
【技术难点】:TCP打洞
【参考网上TCP打洞理论的主要流程】(以下流程为转载内容):
我们先假设一下:有一个服务器S在公网上有一个IP,两个私网分别由NAT-A和NAT-B连接到公网,NAT-A后面有一台客户端A,NAT-B 后面有一台客户端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连接建立起来了。
【问题】:
1.我在服务器开启两个ServerSocket,并且都绑定不同的端口进行监听,但服务器运行后,客户端连接时,服务器会报错“Unrecognized Windows Sockets error.”这个错误,百度了一下说是端口被占用了,但我已经在绑定端口前都调用了ServerSocket.setReuseAddress(true);
这里我顺手附上使用SO_REUSEADDR选项时的两点注意事项:
①必须在调用bind方法之前使用setReuseAddress方法来打开SO_REUSEADDR选项。因此,要想使用SO_REUSEADDR选项,就不能通过Socket类的构造方法来绑定端口。
②必须将绑定同一个端口的所有的Socket对象的SO_REUSEADDR选项都打开才能起作用。
2.在客户端中,作为A用户,我在连接【主连接】服务器登录后,第一次向B用户发送信息时,就开启线程连接【协助打洞】服务器,第一次点击发送无任何反应,第二次发送就报“Thread already started”这个错误,而我在调用线程前就做了判断
if(!assistance_Thread.isAlive){
assistance_Thread.start();
}。有些人说加上Thread.sleep(100);延迟一下,尝试了没用。