[转]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

 

posted @ 2017-02-28 14:42  遮半世流离  阅读(513)  评论(0)    收藏  举报