一、前言
平常网络编程中,我们常会有需要获取某一个域名ip的需要,比如百度的域名为www.baidu,com
,但不知道它的ip
地址,我们也没办法在代码中用socket
连接上去。
所以本文封装了一个跨平台的函数用于获取对应域名的所有ip
地址,方便大家使用
二、最终效果
首先演示一下封装后的效果,代码如下:
#include<iostream>
#include"domain.h"
using namespace std;
int main() {
auto ret = GetIPAddresses("www.baidu.com");
for (auto i : ret) {
cout << i << endl;
}
}
结果:
其中的domain.h
这个头文件中封装的便是这个完整的函数,代码在后文,你可以直接将其复制粘贴到你自己的一个头文件中,然后包含使用即可。
三、原理
这个封装的函数主要用到了函数getaddrinfo
,它的作用就是通过域名,来查询其对应的ip
地址
其在msvc编译器的头文件ws2tcpip.h
中,在g++编译器的头文件netdb.h
中。
它有四个参数,依次为:
pNodeName
:表示要解析的主机名或 IP 地址(一般为域名)。如果该值为NULL
,则将返回本地主机的地址信息。pServiceName
:表示服务名称或端口号。可以是一个服务名称(如 “http”),也可以是一个十进制的端口号字符串(如 “80”)。如果该值为NULL
,则表示不关心服务名称或端口号。pHints
:表示一些筛选条件,用于帮助获取特定类型的地址信息。它是一个addrinfo
结构体类型,可以通过设置该结构体中的成员变量来指定筛选条件。如果该值为NULL
,则表示没有任何筛选条件。ppResult
:表示返回的地址信息链表。该链表中包含了符合条件的所有地址信息列表。如果函数执行成功,该链表将被填充,否则该链表将为空。
一般来说,我们只需要填写第一个参数和第四个参数即可,即:通过域名,获取ip地址结果。
不过在后面的代码中,我还用到了第三个参数,来限定条件,但大多数时候并没有什么具体的区别。
addrinfo hints {};
hints.ai_family = AF_UNSPEC; // 不限定IPv4或IPv6
hints.ai_socktype = SOCK_STREAM; // 使用TCP协议
唯一需要注意的就是第4个参数,因为那是将要返回的查询结果,定义如下:
typedef struct addrinfo
{
int ai_flags; // AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST
int ai_family; // PF_xxx
int ai_socktype; // SOCK_xxx
int ai_protocol; // 0 or IPPROTO_xxx for IPv4 and IPv6
size_t ai_addrlen; // Length of ai_addr
char * ai_canonname; // Canonical name for nodename
_Field_size_bytes_(ai_addrlen) struct sockaddr * ai_addr; // Binary address
struct addrinfo * ai_next; // Next structure in linked list
}ADDRINFOA, *PADDRINFOA;
实际上,它就是一个包含每一个结果的链表,其中的ai_next
就保存的下一个记录的地址,如果为NULL
就说明后面没有了。
其中主要需要注意的就是ai_addr
字段,这个里面就存放着查询到的ip地址,但其是二进制形式,我们后面还需要将其转化为点分十进制,比如127.0.0.1
这种形式,方便我们查看。
至于其他字段,则都是一些描述信息,比如ai_family
表示当前这个地址用的什么协议(ipv4
、ipv6
等等),ai_socktype
代表使用的是什么类型的socket
(tcp
、udp
等等)
所以后面代码中使用的一个for
循环来遍历这个链表:
for (addrinfo* ptr = result; ptr != NULL; ptr = ptr->ai_next)
只要它不为NULL
,就说明它的后面还有值,就一直进行遍历,直到遍历所有查询到的数据。
其内部还判断了一下它是ipv4还是ipv6的地址:
switch (ptr->ai_family)