[转]UDP穿透
NAT简介
NAT(Network Address Translation ,网络地址转换) 是一种广泛应用的解决IP 短缺的有效方法, NAT 将内网地址转和端口号换成合法的公网地址和端口号,建立一个会话,与公网主机进行通信。
1.NAT分类
(1 )全锥NAT :全锥NAT 把所有来自相同内部IP 地址和端口的请求映射到相同的外部IP 地址和端口。任何一个外部主机均可通过该映射发送数据包到该内部主机。
(2 )限制性锥NAT :限制性锥NAT 把所有来自相同内部IP 地址和端口的请求映射到相同的外部IP 地址和端口。但是, 和全锥NAT 不同的是:只有当内部主机先给外部主机发送数据包, 该外部主机才能向该内部主机发送数据包。
(3 )端口限制性锥NAT :端口限制性锥NAT 与限制性锥NAT 类似, 只是多了端口号的限制, 即只有内部主机先向外部地址:端口号对发送数据包, 该外部主机才能使用特定的端口号向内部主机发送数据包。
(4 )对称NAT :对称NAT 与上述3 种类型都不同, 不管是全锥NAT ,限制性锥NAT 还是端口限制性锥NAT ,它们都属于锥NAT (Cone NAT )。当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, 对称NAT 会重新建立一个Session ,为这个Session 分配不同的端口号,或许还会改变IP 地址。
2.穿透过程
首先,对于P2P的穿透,需要了解到全锥NAT、限制性锥NAT和端口限制性锥NAT都可以实现P2P的穿透,而对称NAT则无法进行P2P穿透,需要通过服务器转发的形式完成两台NAT后设备之间的通信。
完成P2P的过程如下:
1.在公网上建立一个中转服务器S,A、B均与S先建立连接并发送数据报。
2.服务器将获取到的地址信息按照用户自定的格式分发给B、A
3.A通过从S获取的B的地址信息向B发起连接请求,这次的连接请求会被B的NAT拒绝并丢弃,但在A的NAT上会建立一个B的地址信息,A会接受B向他发送的信息
4.B通过从S获取的A的地址信息向A发起连接请求,这次连接正常情况下会成功
5.当A收到B的数据报后,从中获取B的信息,再次向B发送信息,验证NAT确实被穿透
针对全锥NAT、限制性锥NAT、端口限制性锥NAT和对称NAT都有着明确的协议定义了穿透方案。对于全锥NAT、限制性锥NAT、端口限制性锥NAT可以使用STUN协议完成穿透;对于对称NAT需要使用TURN协议完成穿透。两者的不同在于STUN协议可以直接完成PC的互联以及数据的交互,TURN协议则必须借助中转服务器进行数据的转发。
从当前的商业应用来看,大多数的聊天通信软件选择ICE协议(TURN和STUN的集合)进行P2P通信,而条件允许时则直接使用TURN协议确保穿透的效率和稳定性。
3.实现穿透
在实际进行穿透操作时,需要考虑到以下两种情况
1.A、B在不同NAT下(包括一台在公网)
2.A、B在同一NAT下
对于第一种情况,直接进行穿透就好。
对于第二种情况,则需要对A、B所在的相对位置进行验证,如果确实在同一NAT下,则服务器S只需要将它俩的内网地址互相告知,省去穿透的步骤
值得一提的是,java使用socket.getLocalAddress()所获得的本地ip往往不准确,分享一段找到的获取本地ip的代码
package testUDT; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * IP处理工具类 * * @author Neil * */ public class IPUtils { /** * 获取当前IP * * @return IP */ public static String getCurrenIP() { String[] ips = getIpArray(getLocalIPList()); String curr_ip = "0.0.0.0"; if (ips != null) { curr_ip = ips[0]; } else { try { InetAddress net = InetAddress.getLocalHost(); if (net != null) { curr_ip = net.getHostAddress(); } } catch (UnknownHostException e) { e.printStackTrace(); } } return curr_ip; } /** * 获取本地IP列表 * * @return 返回本地IP列表 */ public static List<String> getLocalIPList() { List<String> ipList = new ArrayList<String>(); try { Enumeration<NetworkInterface> networkInterfaces = NetworkInterface .getNetworkInterfaces(); NetworkInterface networkInterface; Enumeration<InetAddress> inetAddresses; InetAddress inetAddress; String ip; while (networkInterfaces.hasMoreElements()) { networkInterface = networkInterfaces.nextElement(); inetAddresses = networkInterface.getInetAddresses(); while (inetAddresses.hasMoreElements()) { inetAddress = inetAddresses.nextElement(); if (inetAddress != null && inetAddress instanceof Inet4Address) { // IPV4 ip = inetAddress.getHostAddress(); ipList.add(ip); } } } } catch (SocketException e) { e.printStackTrace(); } System.out.println(ipList); return ipList; } /** * 将ip列表转换成数组 * * @param list * 用List记录的ip列表 * @return 返回用数组形式的ip列表 */ public static String[] getIpArray(List<String> list) { if (list == null) { return null; } String[] arr = new String[list.size()]; int i = 0, j = 0; for (String s : list) { arr[i++] = s; if (s.matches("192\\.168\\.10*.\\d{1,3}")) { j = i - 1; } } String tem = arr[0]; arr[0] = arr[j]; arr[j] = tem; return arr; } /** * 将ip从String类型转化为InetAddress类型 * * @param strIp String类型的ip地址 * @return 返回InetAddress类型的ip地址 */ public static InetAddress stringToInetAddress(String strIp) { int[] intAddr = new int[4]; int[] intAddrOK = new int[4]; String[] strAddrOK = new String[4]; byte[] byteAddrOK = new byte[4]; InetAddress ia = null; // 将String用正则表达式进行分解 String[] strArray = strIp.split("\\."); for (int i = 0; i < 4; i++) { intAddr[i] = Integer.valueOf(strArray[i]).intValue(); if ((intAddr[i] <= 127) && (intAddr[i] >= -127)) intAddrOK[i] = intAddr[i]; else intAddrOK[i] = intAddr[i] - 256; // int 转换到 string strAddrOK[i] = String.valueOf(intAddrOK[i]); // string 转换到byte byteAddrOK[i] = Byte.parseByte(strAddrOK[i]); } try { ia = InetAddress.getByAddress(byteAddrOK); } catch (UnknownHostException e) { e.printStackTrace(); } return ia; } }
如果有兴趣,可以搜索PCDClient,很抱歉忘记了原作者的名字,源码应该是C++编写的
如果感兴趣,可以查看以下两篇其他博主的博客
http://blog.chinaunix.net/uid-10995039-id-166028.html
http://www.iteye.com/topic/1128897

浙公网安备 33010602011771号