前言
因爲工作需要,我們需要處在局域網中的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