Android底層獲取設備局域網IP

前言

因爲工作需要,我們需要處在局域網中的Android設備自己在底層獲取IP地址,網上有很多的說法是通過WifiManager來拿到設備的局域網IP.方法雖然行得通,但是不適合我當前的場景,所以需要想別的方式。

網絡IP

所有在網絡中【廣域網,城域網,局域網等等】裏面的設備之間要通信就必須擁有一個能被網絡識別的IP地址。

私有網絡 IP
  • 私有IP就是在局域網內部分配的IP地址,不能直接訪問Internet ;對應的就是公有IP,可以訪問Internet;
  • 我們都知道IP地址被分爲A,B,C,D,E五類,其中就從A,B,C類中劃分出了一部分作爲私有ip,供局域網分IP地址分配。當前局域網中的ip只在當前網絡中有效。
  • 私有IP的範圍
    A:10.0.0. 0 ~ 10.255.255.255 【10.0.0.0/8】
    B:172.16.0…0 ~ 172.31.255.255 【172.16.0.0/12】
    C:192.168.0.0 ~ 193.168.255.255 【192.168.0.0/16】

私有IP的使用

我們常見的家庭網絡中,路由器會構建一個小型的局域網,這個網絡中的設備一般來說都是是很少的,所有一般就會使用C類私有IP。你家的主機IP【win: CMD-> ipconfig; Linux:terminal->ifconfig】是192.168.xx.xx.我家的主機IP也是192.168.xx.xx.即使是一樣也不會有問題,因爲這是局域網私有IP。
稍微大一點的組織或者公司,在構建內部局域網的時候可能會使用B類IP;再大一點的,比如說大學,政府等對網絡IP需求更大,在構建內部局域網的時候就會使用A類私有地址。

而我們要和外部的互聯網通信,這就是路由器和交換機管的事情了,我們不是專門進行網絡通信的開發,知道是誰的事就行,專注當前的工作。

獲取局域網IP

進過簡單把IP的概念的敘述後,再回到我們的需求上來,我的需求是在底層(C++實現)來直接拿到當前Android設備所處局域網內部分配的私有IP,

Idea 1 gethostbyname()通過域名解析IP

我一開始的想法是使用的Linux 提供的gethostname()和gethostbyname()函數,gethostname()函數可以獲取在本地主機的信息我們可以通過它拿到設備的名稱;然後gethostbyname()函數可以通過gethostname()函數返回的設備名稱來獲取設備的IP。

不過在機器上驗證的時候,就發現了問題;gethostname()可以返回設備的名稱,但是gethoastbyname()返回的就是不是設備所在網絡下的IP,而是127.0.1.1,這個127.0.1.1是在/etc/hosts文件中。和設備名稱相匹配的,我試了將/etc/hosts文件中的127.0.1.1修改成其他的IP值,相應的gethostbyname()得到的也是修改後值。當然會也可以修改設備名稱。

示例代碼
/* 
     test gethostname and gethostbyname
 */
#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//
using namespace std;

int main()
{
    const size_t deviceNameLen = 1024;
    char hostName[deviceNameLen] = {0};
    int result = gethostname(hostName, deviceNameLen);
    if (0 != result)
    {
        cout << "error !!!" << endl;
    }
    cout << hostName << endl;
    string nameStr = hostName;
    string name1 = "xxxx-OptiPlex-7050"; //已知的hostname
    cout << "相等=" << name1.compare(nameStr) << endl;
    cout << nameStr.length() << endl;

    struct hostent *hp;
    if ((hp = gethostbyname(hostName)) == NULL)
    {
        cout<<"gethostbyname"<<endl;
    }
    int i = 0;
    while (hp->h_addr_list[i] != NULL)
    {   
        cout<<"hostname: "<< hp->h_name<<endl;
        cout<<"    ip:"<< inet_ntoa(*(struct in_addr *)hp->h_addr_list[i])<<endl;
        i++;
    }
    return 0;
}

gethostbyname()其實就是一個典型的DNS(Domain Name Service )【域名服務】。就是通過簡單易記的域名來得到這個額名字代表的IP。
所以,gethostnyname()得到的是一個在設備上已經定好的一個“靜態IP”,顯然不是我之前的需求,要得到一個局域網分配的IP那就得想其他的辦法。

Idea 2 通過ioctol()獲取設備IP

其實,Linux C/C++中已經爲我們提供了其他的辦法【由於主要實在linux上進行開發驗證的,window的及其它系統的具體接口情況不是很清楚,不過肯定也是提供了大量的現成的接口供調用以實現相應的功能】,那就是linux C/C++中定義了很多的結構讓我們使用,

  1. 其中有個struct ifreq,這是個用於socket ioctl請求的結構。
struct ifreq
  {
# define IFHWADDRLEN	6
# define IFNAMSIZ	IF_NAMESIZE
    union
      {
	char ifrn_name[IFNAMSIZ];	/* Interface name, e.g. "en0".  */
      } ifr_ifrn;
    union
      {
	struct sockaddr ifru_addr;
	struct sockaddr ifru_dstaddr;
	struct sockaddr ifru_broadaddr;
	struct sockaddr ifru_netmask;
	struct sockaddr ifru_hwaddr;
	short int ifru_flags;
	int ifru_ivalue;
	int ifru_mtu;
	struct ifmap ifru_map;
	char ifru_slave[IFNAMSIZ];	/* Just fits the size */
	char ifru_newname[IFNAMSIZ];
	__caddr_t ifru_data;
      } ifr_ifru;
  };

struct ifreq裏面有很多變量和結構,我們目前只針對我們現在需要的進行講解,其他的暫時先放一放,
ifr_ifrn.ifrn_name:這是個char類型的數組,表示設備擁有的網卡名稱,一般設備有多個網卡。
ifr_ifru.ifru_addr :這是一個結構體,描述通用套接字地址的結構,保存有IP.
在<net/if.h>有宏定義:

...
# define ifr_name	ifr_ifrn.ifrn_name	/* interface name 	*/
...
# define ifr_addr	ifr_ifru.ifru_addr	/* address		*/
...

所以,我們也可以用變量ifr_name ,ifr_addr分表表示 ifr_ifrn.ifrn_name,ifr_ifru.ifru_addr這就根據個人的編程習慣選擇自己喜歡的方式。只是先看其他代碼是時候,我們要知道ifr_name就是ifr_ifrn.ifrn_name…。
2.還有一個結構struct ifconf

/* Structure used in SIOCGIFCONF request.  Used to retrieve interface
   configuration for machine (useful for programs which must know all
   networks accessible).  */

struct ifconf
  {
    int	ifc_len;			/* Size of buffer.  */
    union
      {
	__caddr_t ifcu_buf;
	struct ifreq *ifcu_req;
      } ifc_ifcu;
  };
# define ifc_buf	ifc_ifcu.ifcu_buf	/* Buffer address.  */
# define ifc_req	ifc_ifcu.ifcu_req	/* Array of structures.  */

這個結構體主要用於ioctol()的SIOCGIFCONF請求,用於檢索接口機器的配置(對於必須知道所有信息的程序有用網絡可用)

示例代碼
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netdb.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <iostream>
#include <netinet/in.h>
#include <string.h>

using namespace std;
size_t get_local_ip(string *ip_addr)
{
    size_t sfd, intr;
    struct ifreq buf[16];
    struct ifconf ifc;
    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd < 0)
    {
        return -1;
    }
    ifc.ifc_len = sizeof(buf);
    ifc.ifc_buf = (caddr_t)buf;
    if (ioctl(sfd, SIOCGIFCONF, (char *)&ifc))
    {
        return -1;
    }
    intr = ifc.ifc_len / sizeof(struct ifreq);
    for (; intr-- > 0; intr > 0)
    {
        ioctl(sfd, SIOCGIFADDR, (char *)&buf[intr]);
        //char *ifname = (char *)&buf[intr].ifr_ifrn.ifrn_name;
        char *ifname = (char *)&buf[intr].ifr_name;
        // enp0s31f6是我當前主機上的網卡,其他主機驗證當前代碼需要根據自己網卡修改
        if (strcmp(ifname, "enp0s31f6") == 0)
        {
            break;
        }
    }

    close(sfd);
    // in_addr_t ipp = ((struct sockaddr_in *)(&buf[intr].ifr_addr))->sin_addr.s_addr;
    // in_addr *add = new in_addr();
    // add->s_addr = ipp;
    // inet_ntoa()函數將傳入的Internet數字轉換爲ASCII表示。返回值是指向包含字符串的內部數組的指針。
    // char *ip = inet_ntoa(*add);
    char *ip = inet_ntoa(((struct sockaddr_in *)(&buf[intr].ifr_addr))->sin_addr);
    *ip_addr = ip;
    // delete add;
    return 0;
}

int main()
{
    string ip = "";
    size_t result = get_local_ip(&ip);
    if (0 != result)
    {
        cout << "get ip error!!" << endl;
    }
    cout << "ip=" << ip << endl;
    cout << "ip length =" << ip.length() << endl;
    return 0;
}
本地驗證可用,可以根據網卡要求獲取所需的IP地址,當然也可以獲取我當前需求的局域網IP。
idea 3 通過getifaddrs()函數獲取IP

我們還可以使用getifaddrs()函數來實現

extern int getifaddrs (struct ifaddrs **__ifap) __THROW;

這個方法創建struct ifaddrs結構的鏈接列表,每個結構一個主機上的網絡接口。如果成功,則存儲在IFAP中列出並返回0。出現錯誤時,返回-1並設置’errno’。在IFAP中返回的存儲是動態分配的,可以只有通過將其傳遞給“freeifaddrs”才能正確釋放。
struct ifaddrs結構定義如下所示:

/* The `getifaddrs' function generates a linked list of these structures.
   Each element of the list describes one network interface.  */
struct ifaddrs
{
  struct ifaddrs *ifa_next;	/* Pointer to the next structure.  */

  char *ifa_name;		/* Name of this network interface.  */
  unsigned int ifa_flags;	/* Flags as from SIOCGIFFLAGS ioctl.  */

  struct sockaddr *ifa_addr;	/* Network address of this interface.  */
  struct sockaddr *ifa_netmask; /* Netmask of this interface.  */
  union
  {
    /* At most one of the following two is valid.  If the IFF_BROADCAST
       bit is set in `ifa_flags', then `ifa_broadaddr' is valid.  If the
       IFF_POINTOPOINT bit is set, then `ifa_dstaddr' is valid.
       It is never the case that both these bits are set at once.  */
    struct sockaddr *ifu_broadaddr; /* Broadcast address of this interface. */
    struct sockaddr *ifu_dstaddr; /* Point-to-point destination address.  */
  } ifa_ifu;
  /* These very same macros are defined by <net/if.h> for `struct ifaddr'.
     So if they are defined already, the existing definitions will be fine.  */
# ifndef ifa_broadaddr
#  define ifa_broadaddr	ifa_ifu.ifu_broadaddr
# endif
# ifndef ifa_dstaddr
#  define ifa_dstaddr	ifa_ifu.ifu_dstaddr
# endif

  void *ifa_data;		/* Address-specific data (may be unused).  */
};
示例代碼

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <ifaddrs.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>


std::string getIPAddress()
{
    std::string ipAddress = "Unable to get IP Address";
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    size_t success = 0; // retrieve the current interfaces - returns 0 on success
    success = getifaddrs(&interfaces);
    if (success == 0)
    {
        std::cout << __LINE__ << std::endl;
        // Loop through linked list of interfaces
        temp_addr = interfaces;
        std::cout << temp_addr << std::endl;
        while (temp_addr != NULL)
        {
            std::cout << __LINE__ << std::endl;
            if (temp_addr->ifa_addr->sa_family == AF_INET)
            {
                std::cout << temp_addr->ifa_name << std::endl;
                // Check if interface is en0 which is the wifi connection on the iPhone
                if (strcmp(temp_addr->ifa_name, "wlan0") == 0)
                {
                    std::cout << __LINE__ << std::endl;
                    ipAddress = inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr);
                    goto raturn_ip;
                }
            }
            temp_addr = temp_addr->ifa_next;
        }
    }

raturn_ip:
    // Free memory
    freeifaddrs(interfaces);
    return ipAddress;
}

size_t main()
{
    std::string ip = getIPAddress();
    std::cout << "ip = " << ip << std::endl;
}

以上我們找到了三種方式獲取IP。第一種是通過域名倆獲取IP,獲取不到局域網IP。它就是一個典型的DNS;第二種是通過ioctol()來獲取設備IP,包括局域網IP;第三種方法直接通過getifaddrs()函數獲取IP,包括局域網IP

發佈了42 篇原創文章 · 獲贊 35 · 訪問量 8009
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章