前言
因为工作需要,我们需要处在局域网中的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++中定义了很多的结构让我们使用,
- 其中有个
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