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

发布了41 篇原创文章 · 获赞 35 · 访问量 4296

猜你喜欢

转载自blog.csdn.net/weixin_38140931/article/details/103926076