用java实现tcp穿越nat,很多资料,众说纷纭,希望大家一起动手验证

昆卡卡 2013-06-26 05:56:11
如题,我实现的过程描述一下,有A.B两个客户端分别挂在NA,NB两个nat下面,通过固定IP的服务器S,交换A,B客户端外的nat的公网IP和端口,然后由B向A发起连接,进行打洞(这次绝大多数应该是失败的,我也确实失败了,超时),然后由A再连接B,理论上B已经打过通往A的洞了,这次连接应该能够通过NB跟B连接上,但是我这边失败了,一直连接不上。(以上A,B跟S通讯交换IP已经A,B互相连接和建立监听我都用的同一个端口)。网上有说java的API不支持tcp包组装,所以无法打洞的,我很疑惑,希望做过类似的大家一起过来讨论一下。谢谢
...全文
683 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复
各路大神,我现在也碰到了跟楼主相同的问题,不过需求有点不同。
我需要做一个P2P文件传输的功能,所以上面说的驳接方式应该不适合我。
图中是我的实现过程,望各位帮我分析分析啊,我完全没有头绪了!!!
MiceRice 2013-07-03
  • 打赏
  • 举报
回复
引用 20 楼 yyy269954107 的回复:
谢谢您的解答,我现在准备采用您说的驳接的方式来实现A,B之间的通信,最后想请教一下,如果客户端很多,假如要能承受20000同时连接,采用这种方式还合适么,我想采用mina框架来开发服务端的程序,不知是否可行,还请大神指点一下
要看通讯所承载的数据量,如果20000个连接,都是几小时才发送几个字节的信息,当然可以。如果20000个连接都打算狂发送数据,你的系统肯定撑不住,什么框架都白搭。 所以要通过做性能测试,来进行估算。
昆卡卡 2013-07-02
  • 打赏
  • 举报
回复
先结贴吧,大神有时间帮忙看下,谢谢各位细心的解答
昆卡卡 2013-07-02
  • 打赏
  • 举报
回复
引用 19 楼 ldh911 的回复:
总的来说这种情况意味着所有ClientX都要借助同一个端口进行双向通讯,这就导致不能使用常规单向打洞,只能是做我所说的驳接(也就是转发)。
谢谢您的解答,我现在准备采用您说的驳接的方式来实现A,B之间的通信,最后想请教一下,如果客户端很多,假如要能承受20000同时连接,采用这种方式还合适么,我想采用mina框架来开发服务端的程序,不知是否可行,还请大神指点一下
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
引用 12 楼 ldh911 的回复:
在此之前我需要确认一个问题:默认情况下,公网可以直接连接到 ClientA的22222 和 ClientB的33333 端口上么?也就是你公司的路由,是不是已经给ClientA和B开通的端口映射(其实,也就是打洞)。
公网不能够直接连接到22222和33333,这两个端口我没有做端口映射,现在要实现的就是在两个nat后面的局域网的主机能够自己连接,我理解的打洞就是nat会过滤掉不请自来的包,当ClientB向ClientA发起连接后,就会在自己的nat上面留下一个记录即打洞一段时间,在这段时间内当有ClientA再连接ClientB的时候,ClientB的nat上面因为留有记录,认为这个是刚刚ClientB发出去信息的回应,不会丢掉这个包,即连接成功了
MiceRice 2013-06-27
  • 打赏
  • 举报
回复
引用 9 楼 yyy269954107 的回复:
我先描述一下我的做法吧。
根据你描述的信息来看,有两个问题: 1、貌似你理解的打洞,不是那么个回事; 2、你的问题的难度会更高些,因为服务器只有一个端口可用(11111),相当于只能打1个洞。。。 在此之前我需要确认一个问题:默认情况下,公网可以直接连接到 ClientA的22222 和 ClientB的33333 端口上么?也就是你公司的路由,是不是已经给ClientA和B开通的端口映射(其实,也就是打洞)。
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
引用 7 楼 preferme 的回复:
1.弄清楚服务端和客户端在打孔过程中,各自的任务(所做的事情步骤)。 2.服务端的Socket不用设置成重用,只起到交互在线用户连接的公网地址发布的作用。 3.客户端要完成同一个Socket即向外发起连接又监听本地端口功能,所以,要设置Socket重用, 但是,要注意,Socket重用在多进程中也会生效。 4.客户端的步骤,除了Socket重用外,一般来讲是先向服务端注册本机(本机ID等信息), 这样服务端就会记录在线客户端的相关信息(包括公网地址), 然后服务端传递一些当前的在线用户的信息给客户端, 之后客户端再进行端口监听(这个监听功能的代码会推给另一个线程去做)。 5.打孔过程完毕后,客户端完成向其他在线客户端程序发起连接的操作,进行打孔后的数据交互。
嗯,真正使用时肯定要有ID之类的好让两个客户端匹配的东西的,现在做测试,我就写死了两个客户端,暂时没加上,我把我实现的步骤写出来了,希望看下有什么问题没,如果需要我代码也贴出来
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
引用 8 楼 Caedmon_ 的回复:
要的就是P2P?
嗯,是的,不过不需要JXTA那么强大的功能,只要能让处于两个局域网内的主机能够通过TCP连接上就可以了
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
引用 4 楼 ldh911 的回复:
主要是没搞懂你打算怎么打洞。 首先你需要在服务器上开发一个 TcpServer,实现几个能力: 1、可让ClientX申请在身上开洞(端口),其实就是端口映射; 2、可根据已注册的洞,自动将请求转发给对应ClientX的对应端口; 3、如果增强点,可以提供查询所以已注册端口及对应ClientX的清单。 实际上有些路由器直接支持这种UPnP端口映射,不过要使用标准协议了。 开发TcpServer,既可以网上找些“Java 端口转发 端口映射”的代码,自己设计注册协议;也可以直接安装某些支持UPnP的端口映射软件(不过这样你就得去学习如何用UPnP来开启端口映射的协议了)。 那么流程大概是这样: 1、ClientA向TcpServer申请注册端口,类似于: ClientA:123 -> Server:8080 2、ClientA监听本机123端口; 3、ClientB向TcpServer的注册端口发起TCP请求; 4、TcpServer会直接转发该请求; 5、ClientA接收到服务器转发的请求; 6、借助TcpServer,ClientA和B实现了连接。 找了找没看到TCP的样例,只有个比较简化的UDP样例,不过理念类似: http://www.iteye.com/topic/1128897
这个实现方法跟我的不太一样,我有个疑问,你描述的步骤4,5,6看的不是太懂,第四步TcpServer如何转发该请求,是告诉ClientA,ClientB要发起连接以及ClientB经过nat转换后的公网IP和端口么?第六步能否说的详细一点,ClientA和B是如何实现连接的? 我先描述一下我的做法吧。我的实验环境是公司可以连接外网的局域网两台机子,我家里的电信宽带后面有路由器的一台计算机,我在家里的路由器上做了一个端口映射,端口号为11111,映射到我的计算机,然后根据该次连接的公网IP作为固定IP,供ClientA,B连接,相当于一个固定IP的服务器。 1、家里的TcpServer启动,在端口11111上监听,供客户端注册。 2、ClientB通过绑定端口22222连接TcpServer,TcpServer记录ClientB经过nat转换后的公网IP及端口,TcpServer和ClientB保持连接 3、ClientA通过绑定端口33333连接TcpServer,TcpServer记录ClientA经过nat转换后的公网IP及端口,TcpServer将之前记录的ClientB的公网IP和端口告诉ClientA后断开与A的连接,然后将ClientA的公网IP和端口告诉ClientB,断开与B的连接。 4、ClientB收到TcpServer返回的A的公网IP和端口后在端口22222上建立监听,并且绑定22222端口后connect连接A(这一步应该就是打洞,一般情况会失败)。 5、ClientA看到第四步ClientB连接A超时后,通过先前拿到的ClientB的公网IP和端口,绑定33333端口后connect连接ClientB(理论上应该能够穿透打洞后的nat与ClientB连接上,但是以超时告终) 说明:由于是测验没做太完善,第五步判断ClientB连接超时是通过我人工判断的,然后在人工让ClientA去连接ClientB的。 理论和方法是在网上看的,这种穿透只能成功穿透非对称型的nat,希望前辈看下, 大家也看下,有什么问题
Caedmon_ 2013-06-27
  • 打赏
  • 举报
回复
要的就是P2P?
冰思雨 2013-06-27
  • 打赏
  • 举报
回复
1.弄清楚服务端和客户端在打孔过程中,各自的任务(所做的事情步骤)。 2.服务端的Socket不用设置成重用,只起到交互在线用户连接的公网地址发布的作用。 3.客户端要完成同一个Socket即向外发起连接又监听本地端口功能,所以,要设置Socket重用, 但是,要注意,Socket重用在多进程中也会生效。 4.客户端的步骤,除了Socket重用外,一般来讲是先向服务端注册本机(本机ID等信息), 这样服务端就会记录在线客户端的相关信息(包括公网地址), 然后服务端传递一些当前的在线用户的信息给客户端, 之后客户端再进行端口监听(这个监听功能的代码会推给另一个线程去做)。 5.打孔过程完毕后,客户端完成向其他在线客户端程序发起连接的操作,进行打孔后的数据交互。
冰思雨 2013-06-27
  • 打赏
  • 举报
回复
服务端参考样例代码
	public static void serverDemo() throws IOException{
		final String SERVER_IP = "";
		final int SERVER_PORT = 60000;
		SocketAddress localAddr = new InetSocketAddress(SERVER_IP,SERVER_PORT);
		ServerSocket ss = new ServerSocket();
		ss.bind(localAddr);
		while(true){
			Socket s = ss.accept();
			createThreadToAccept(s);
		}
	}
	private static void createThreadToAccept(Socket s) {
		ClientInfo info = readRegistInfo(s);
		info.setSocketAdress(s.getRemoteSocketAddress());
		doRegist(info);
		List<SocketAddress> online = findOnlineAddresses();
		writeOnlineAddresses(online);
	}
冰思雨 2013-06-27
  • 打赏
  • 举报
回复
客户端参考样例代码

                final int LOCAL_PORT = 8880;
		SocketAddress localAddr = new InetSocketAddress(LOCAL_PORT);
		ServerSocket ss = new ServerSocket();
		ss.setReuseAddress(true);
		ss.bind(localAddr);
		Socket socket = new Socket();
		socket.setReuseAddress(true);
		socket.bind(localAddr);
		final String SERVER_IP = "";
		final int SERVER_PORT = 60000;
		SocketAddress serverAddr = new InetSocketAddress(SERVER_IP,SERVER_PORT);
		socket.connect(serverAddr);
		sendAndRegist(socket);
		List<InetSocketAddress> addresses = receiveAndGetAddresses(socket);
		createThreadToDoSomeThing(socket,addresses);
		while(true){
			Socket s = ss.accept();
			createThreadToAccept(s);
		}
MiceRice 2013-06-27
  • 打赏
  • 举报
回复
主要是没搞懂你打算怎么打洞。 首先你需要在服务器上开发一个 TcpServer,实现几个能力: 1、可让ClientX申请在身上开洞(端口),其实就是端口映射; 2、可根据已注册的洞,自动将请求转发给对应ClientX的对应端口; 3、如果增强点,可以提供查询所以已注册端口及对应ClientX的清单。 实际上有些路由器直接支持这种UPnP端口映射,不过要使用标准协议了。 开发TcpServer,既可以网上找些“Java 端口转发 端口映射”的代码,自己设计注册协议;也可以直接安装某些支持UPnP的端口映射软件(不过这样你就得去学习如何用UPnP来开启端口映射的协议了)。 那么流程大概是这样: 1、ClientA向TcpServer申请注册端口,类似于: ClientA:123 -> Server:8080 2、ClientA监听本机123端口; 3、ClientB向TcpServer的注册端口发起TCP请求; 4、TcpServer会直接转发该请求; 5、ClientA接收到服务器转发的请求; 6、借助TcpServer,ClientA和B实现了连接。 找了找没看到TCP的样例,只有个比较简化的UDP样例,不过理念类似: http://www.iteye.com/topic/1128897
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
引用 1 楼 ldh911 的回复:
这排版,看得眼晕。。。 你是怎么打洞的?
大神希望你关注一下啊,我B向A打洞就是B先端口重用,再bind之前所用的通讯端口,然后connect客户端A的公网IP和端口
MiceRice 2013-06-27
  • 打赏
  • 举报
回复
引用 15 楼 yyy269954107 的回复:
你说的驳接我理解的意思是A跟CenterServer先建立一个通道,B也跟CenterServer建立一个通道,然后CenterServer把这两个通道连接起来?那是不是意思是以后A跟B所有的通讯都需要经过CenterServer的转发了?
是的。实际上,只要是打洞,所有通讯都会要经过CenterServer转发,简单点说就是消耗CenterServer的网络流量;这是打洞所无法避免的,切记。 简化处理的单向打洞其实也是可以,就如16楼童鞋所说。 但是因为你可怜的CenterServer只有一个端口(111111)可供使用(暴露于公网),所以如果被比如ClientA所注册绑定掉,那么就不能再提供给任何ClientX去注册了,后续所有请求到CenterServer端口11111的连接都被无脑转给ClientA了。 总的来说这种情况意味着所有ClientX都要借助同一个端口进行双向通讯,这就导致不能使用常规单向打洞,只能是做我所说的驳接(也就是转发)。
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
回答问题的两位大神暂时不在,有其他人做过类似的东西的么,谨听教诲啊
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
引用 16 楼 preferme 的回复:
A、B、S 三个方向,S方向不用打孔。剩下的,你是要做双向打孔,还是单向打孔 ? 打孔主要解决的是入站连接的问题,而TCP连接是全双工的,既可以发送消息也可以接收消息。 所以,单向打孔,是能够完成消息的互通的。 单向打孔,你是要A=>B方向的,还是要B=>A方向的 ? 双向打孔,我觉得,楼主说的协议结构有些过于简单了,客户端ID是必须要有的东西。并且连入的客户端地位是相同的,业务处理没有先后或者连带关系。
嗯,ID是必须要有的,如你所说只需要A,B相连的话是地位相等的,但是我这样实现有业务需求在里面。我双向打孔和单向打孔都试过,打孔以后无法连接。您说的单向打孔应该是我需要的,我理解的是如果想A连接上B,应该先由B向A打一个孔,然后A才能连接上B,我就卡在打完孔以后A连接不上B,超时
冰思雨 2013-06-27
  • 打赏
  • 举报
回复
A、B、S 三个方向,S方向不用打孔。剩下的,你是要做双向打孔,还是单向打孔 ? 打孔主要解决的是入站连接的问题,而TCP连接是全双工的,既可以发送消息也可以接收消息。 所以,单向打孔,是能够完成消息的互通的。 单向打孔,你是要A=>B方向的,还是要B=>A方向的 ? 双向打孔,我觉得,楼主说的协议结构有些过于简单了,客户端ID是必须要有的东西。并且连入的客户端地位是相同的,业务处理没有先后或者连带关系。
昆卡卡 2013-06-27
  • 打赏
  • 举报
回复
引用 14 楼 ldh911 的回复:
简化版本的话,就是不考虑控制信道,CenterServer只管将第一个连接上来的客户端的输入输出,驳接到第二个连接上来的客户端的输出输入。
你说的驳接我理解的意思是A跟CenterServer先建立一个通道,B也跟CenterServer建立一个通道,然后CenterServer把这两个通道连接起来?那是不是意思是以后A跟B所有的通讯都需要经过CenterServer的转发了?还是经过驳接A跟B能够建立起来直接的通道,不用CenterServer转发?如果是后者的话麻烦讲述下驳接的过程好么,这方面没接触过。如果是需要转发的话不是我想要的效果,这样服务器的负担就太大了
加载更多回复(3)

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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