浅谈公网IP、内网IP、NAT转换、MAC地址的获取

6,880 阅读7分钟

一、引言

在日常开发中会经常听到,某些系统(支付系统、反欺诈系统)要求客户端上送:公网IP(即互联网IP地址)和内网IP(即局域网IP地址),它们分别是什么呢?有什么区别的?又有什么联系呢?另外,提到公网IP和内网IP,就不得不提NAT路由转换,NAT又是什么呢?本文就来简单讲讲这些到底都是怎么回事。

二、公网IP和内网IP的联系

每台电脑都必须要一个公网IP吗?

不是。我们都知道,IPv4中的IP地址的数量是有限的(所以现在都在搞IPv6),每次把一部分地址分配出去,那么就意味着能够用来分配的IP地址就更少了,而且随着现在手机、电脑等智能设备的快速发展,如果每个手机或者电脑都要求一个IP地址,那么显然IP地址是不够用的。

为了解决这个问题,我们可以采取这样的策略:例如对于一个公司来说,每个公司都会有一个属于自己公司的内网(也可以称之为局域网)。

内网(学名叫局域网)是在一个局部的地理范围内,一般可以是是几米内(比如家庭内网),也可以是方圆几千米以内(比如一个大学内网),将各种计算机、外部设备和数据库等互相联接起来组成的计算机通信网。

三、内网IP的作用

1、内网主要作用有哪些?

  • 共享传输信道:简单地理解就是不需要每台电脑一个外网IP地址。
  • 传输速率高:内网之间的电脑因为没有外网网络拓扑的复杂性,所以互相通信的网络可以很快。
  • 误码率低:因为通信距离很近,所以误码率很低,换句话说就是网络很稳定。

2、公司的内网是如何实现内网IP地址分配和管理的?

假如我们给公司A分配了一个IP=192.168.1.1。我们把这个IP作为公司A内网的网关吧。 在公司A的内网里面有3台电脑,如果这三台电脑要上网的话,我们需要给他分配一个IP,那么就像上一节提到的:我们一定需要去申请3个IP地址来使用吗?

我们不一定需要去申请3个IP的,在我们这个内网里,我们可以指定自己的规则,例如,我们可以给这三台电脑随便分配三个IP(请注意,这三个IP不是去申请的,而且我自己随意给它分配的)。分别分配电脑A = 192.168.1.2 电脑B = 192.168.1.3 电脑C = 192.168.1.4。

而这个规则可以由我们的内网网关来管理,就像下面这样:

四、NAT技术-实现内网电脑访问外网的能力

假如电脑A想要访问百度,百度的IP我们假设为:172.168.30.3,我们都知道,电脑A的IP是我们虚构的,实际上可能并不存在这样一个IP,如果用电脑A的IP去访问百度,那肯定行不通。我们也知道,由于百度和电脑A不在一个局域网内,所以A要访问百度,那么必须得经过网关。而网关的这个IP地址,是真实存在的,是可以访问百度的。

为了让电脑可以访问百度,那么我们可以采取这样的方法:让网关去帮助电脑A访问百度,百度把结果传递给网关,然后网关再把结果传递给电脑A,这样不就可以解决了?

不过电脑A、B、C都可能拜托网关去帮忙访问百度,而百度返回的结果的目标IP都是网关的IP=192.168.1.1。那么网关该如何进行区分这结果是电脑A、B还是C的呢?

我们去访问百度的时候,不是需要指定一个端口吗?只要我们把电脑A的IP + 端口 映射成 网关的IP+端口,不就可以唯一确定身份了?

例如电脑A用端口60去访问百度,网关把电脑A的IP+端口60 映射成 网关的IP+端口80 不就可以了? 百度把结果返回给网关的80端口之后,网关再通过映射表,就可以把结果返回给 A的60端口 了。 如果B也是用60端口去访问百度的话,也是一样,可以把它映射到90端口。

这种方法地址的映射转换,我们也称之为网络地址转换,英文为 Network Address Translation,简称NAT。

而像A、B、C这样的IP地址我们也称之为内网IP,即内网IP;而像网关,百度这样的IP我们称之为外网IP(即互联网公网IP)。

五、公网IP、内网IP、NAT的关系

为了解决IP地址短缺,技术专家们发明了内网技术,而内网技术的理论支撑就是NAT技术。

六、获取公网IP地址

权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>
 /**
   * 方法一:获取外网IP地址--通过搜狐的网址
   * @return
   */
      public void getNetIp() {
          new Thread(){
              @Override
              public void run() {
                  String line = "";
                 URL infoUrl = null;
                 InputStream inStream = null;
                 try {
                     infoUrl = new URL("http://pv.sohu.com/cityjson?ie=utf-8");
                     URLConnection connection = infoUrl.openConnection();
                     HttpURLConnection httpConnection = 
                     	(HttpURLConnection) connection;
                     int responseCode = httpConnection.getResponseCode();
                     if (responseCode == HttpURLConnection.HTTP_OK) {
                         inStream = httpConnection.getInputStream();
                         BufferedReader reader = 
                        	 new BufferedReader(
                        		 new InputStreamReader(inStream, "utf-8"));
                      	 StringBuilder strber = new StringBuilder();
                         while ((line = reader.readLine()) != null)
                             strber.append(line + "\n");
                         inStream.close();
                         // 从反馈的结果中提取出IP地址
                         int start = strber.indexOf("{");
                         int end = strber.indexOf("}");
                         String json = strber.substring(start, end + 1);
                         if (json != null) {
                             try {
                                 JSONObject jsonObject = new JSONObject(json);
                                 line = jsonObject.optString("cip");
                             } catch (JSONException e) {
                                 e.printStackTrace();
                             }
                         }
                         Message msg = new Message();
                         msg.what = 1;
                         msg.obj = line;
                         //向主线程发送消息
                         handler.sendMessage(msg);
                     }
                 } catch (MalformedURLException e) {
                     e.printStackTrace();
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
         }.start();
     }     
 /**
   * 方法二:获取外网IP地址--通过ip168的网址
   * @return
   */
      public void getNetIp() {
          new Thread(){
              @Override
              public void run() {
                 URL infoUrl = null;
                  InputStream inStream = null;
                  String ipLine = "";
                  HttpURLConnection httpConnection = null;
                  try {
                      infoUrl = new URL("http://ip168.com/");
                      URLConnection connection = infoUrl.openConnection();
                      httpConnection = (HttpURLConnection) connection;
                      int responseCode = httpConnection.getResponseCode();
                      if (responseCode == HttpURLConnection.HTTP_OK) {
                          inStream = httpConnection.getInputStream();
                          BufferedReader reader = new BufferedReader(
                                  new InputStreamReader(inStream, "utf-8"));
                          StringBuilder strber = new StringBuilder();
                          String line = null;
                          while ((line = reader.readLine()) != null)
                              strber.append(line + "\n");

                          Pattern pattern = Pattern
                                  .compile("((?:(?:25[0-5]|2[0-4]\\d|((1\\d{2})|
                                  		([1-9]?\\d)))\\.){3}(?:25[0-5]|2[0-4]\\d|
                                		  ((1\\d{2})|([1-9]?\\d))))");
                          Matcher matcher = pattern.matcher(strber.toString());
                          if (matcher.find()) {
                              ipLine = matcher.group();
                          }
                          Message msg = new Message();
                          msg.what = 1;
                          msg.obj = ipLine;
                          //向主线程发送消息
                          handler.sendMessage(msg);
                      }

                  }  catch (MalformedURLException e) {
                               e.printStackTrace();
                           } catch (IOException e) {
                               e.printStackTrace();
                           }
                       }
                   }.start();
               } 

七、获得内网IP地址(本地IP)

获得本地IP地址有两种情况:一是wifi下,二是移动网络下

权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET"/>

wifi下

public void getWifiLocalIPAddress() {
    //获取wifi服务
    WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
    //判断wifi是否开启
    if (!wifiManager.isWifiEnabled()) {
         wifiManager.setWifiEnabled(true);
      }
     WifiInfo wifiInfo = wifiManager.getConnectionInfo();
     int ipAddress = wifiInfo.getIpAddress();
     String ip = intToIp(ipAddress);
     WifiIpView.setText("ip:" + ip);
 }
 
 //获取Wifi ip 地址
 public String intToIp(int i) {
        return (i & 0xFF) + "." +
                ((i >> 8) & 0xFF) + "." +
                ((i >> 16) & 0xFF) + "." +
                (i >> 24 & 0xFF);
 }

移动网络下

public  String getLocalIpAddress() {
    try {
        Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
        while (en.hasMoreElements()) {
             NetworkInterface ni = en.nextElement();
             Enumeration<InetAddress> enIp = ni.getInetAddresses();
             while (enIp.hasMoreElements()) {
                InetAddress inet = enIp.nextElement();
                if (!inet.isLoopbackAddress()&& (inet instanceof Inet4Address)) {
                     Log.v("xjy","另外移动 ip:"+inet.getHostAddress().toString());
                     return inet.getHostAddress().toString();
                 }
             }
          }
      } catch (SocketException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
   }
       return "0";
 }

经过测试,使用getLocalIpAddress该方法,在wifi和移动网络下都能获取到正确的本地IP,所以在获取本地IP有两种写法,第一种直接用getLocalIpAddress方法获取;第二种首先判断网络类型,然后调用上边的两种方法。

//实例化mConnectivityManager对象-获取系统的连接服务
ConnectivityManager mConnectivityManager = 
			(ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE);

//实例化mActiveNetInfo对象-获取网络连接的信息
NetworkInfo mActiveNetInfo = mConnectivityManager.getActiveNetworkInfo();

  if(mActiveNetInfo==null){
      	Log.v("xjy","当前网络不可用,请检查网络");
        AlertDialog mDialog = new AlertDialog.Builder(this)
                     .setTitle("注意")
                     .setMessage("当前网络不可用,请检查网络!")
                     .setPositiveButton("确定", new DialogInterface.OnClickListener() {

                         @Override
                         public void onClick(DialogInterface dialog, int which) {
                             // TODO Auto-generated method stub
                             //关闭对话框
                             dialog.dismiss();
                             //结束Activity
                             finish();
                         }
                     })
                     .create();//创建这个对话框
             mDialog.show();//显示这个对话框
  }else{
      //如果是WIFI网络
      if(mActiveNetInfo.getType()==ConnectivityManager.TYPE_WIFI){
          Log.v("xjy","网络类型:WIFI-IP地址:"+getWifiLocalIPAddress()));
      }
      //如果是手机网络
      else if(mActiveNetInfo.getType()==ConnectivityManager.TYPE_MOBILE){
          Log.v("xjy","网络类型:手机-IP地址:"+getLocalIPAddress()));
      }
      else{
          Log.v("xjy","网络类型:未知-IP地址:");
      }
}

八、获取本地设备Mac地址

public String getMacAddress(){
  /*获取mac地址有一点需要注意的就是android 6.0版本后,通过Context.WIFI_SERVICE方法来获得mac地址。
   不管任何手机都会返回"02:00:00:00:00:00"这个默认的mac地址,
   这是googel官方为了加强权限管理而禁用了getSYstemService(Context.WIFI_SERVICE)方法来获得mac地址。*/

 if (Build.VERSION.SDK_INT >= 23) {
      try {
            List<NetworkInterface> all = 
            		Collections.list(NetworkInterface.getNetworkInterfaces());
            for (NetworkInterface nif : all) {
                 if (!nif.getName().equalsIgnoreCase("wlan0")) continue;

                 byte[] macBytes = nif.getHardwareAddress();
                 if (macBytes == null) {
                      return "";
                 }

                 StringBuilder res1 = new StringBuilder();
                 for (byte b : macBytes) {
                     res1.append(String.format("%02X:", b));
                 }

                 if (res1.length() > 0) {
                      res1.deleteCharAt(res1.length() - 1);
                  }
                 	Log.v("xjy","6.0以上mac地址:"+res1.toString());
                 	return res1.toString();
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             }
             return "02:00:00:00:00:00";

         } else {
             String macAddress = "";
             WifiManager wifiManager = 
        	(WifiManager)getApplicationContext().getSystemService(Context.WIFI_SERVICE);
             WifiInfo wifiInfo = wifiManager.getConnectionInfo();
             macAddress = wifiInfo.getMacAddress();
             Log.v("xjy","6.0以下mac地址:"+macAddress);
             return macAddress;
         }

九、IP和MAC地址总结

MAC地址就是网卡的地址,是手机唯一不变的。手机连接wifi和4G,MAC都不会变,IP地址是变的。