关于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);延迟一下,尝试了没用。
...全文
443 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
wj066000 2018-02-05
  • 打赏
  • 举报
回复
楼主的tcp打洞方法值得学习,以前一直以为UDP打洞更容易实现,读完楼主的思路,看来TCP打洞也是可行的,而且TCP比UDP更可靠一些。 楼主之所以用B直接连A能成功,应该是因为A的路由器NAT转换属于Full Cone,可以接受任何地址的数据,如果是Registed Cone或Port Registed Cone就能和楼主的设想一样了。 但是还有一种特殊的Symmetic NAT,这种NAT环境下,B连向A时路由器分配的端口就不是B连S时的端口了,这种情况恐怕还得需要端口预测的技术来猜测这个新开的端口是多少,否则A无法回连B。
adfldk 2016-09-20
  • 打赏
  • 举报
回复
像毕业设计。。。
沈小池 2016-09-20
  • 打赏
  • 举报
回复
引用 1 楼 adfldk 的回复:
像毕业设计。。。
不是毕业设计。问题也自己解决了。 1.主要是我多new了一个InetSocketAddress对象,多绑定了一个没有用到的端口,删除就没有这个错误了。 2.客户端开启线程连接【协助打洞】服务器,开启方式不对,放弃原来的启动方式,而使用以下这种方式: Assistance_Thread myThread = new Assistance_Thread(); new Thread(myThread).start(); 其中Assistance_Thread是我继承线程的一个类,里面有相关与【协助打洞】的一些操作。这种方式就解决了这个问题。 目前完整的程序还没弄出来,但基本的步骤已经都出来了,现象也有了。就是在【】第6步】B用户尝试A用户时居然直接连接上了。。。。然后现在要加一些逻辑判断在改善一下。

80,351

社区成员

发帖
与我相关
我的任务
社区描述
移动平台 Android
androidandroid-studioandroidx 技术论坛(原bbs)
社区管理员
  • Android
  • yechaoa
  • 失落夏天
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧