pjlib中对地址解析的封装在addr_resolv.h定义,实现在addr_resolv_sock.c和sock_common.c
根据域名获取ip(仅支持IPv4)
struct hostent *gethostbyname(const char *hostname)
struct hostent{
char *h_name; //official name
char **h_aliases; //alias list
int h_addrtype; //host address type
int h_length; //address lenght
char **h_addr_list; //address list
}
从该结构体可以看出,不只返回 IP 地址,还会附带其他信息,各位读者只需关注最后一个成员 h_addr_list。下面是对各成员的说明:
- h_name:官方域名(Official domain name)。官方域名代表某一主页,但实际上一些著名公司的域名并未用官方域名注册。
- h_aliases:别名,可以通过多个域名访问同一主机。同一 IP 地址可以绑定多个域名,因此除了当前域名还可以指定其他域名。
- h_addrtype:gethostbyname() 不仅支持 IPv4,还支持 IPv6,可以通过此成员获取IP地址的地址族(地址类型)信息,IPv4 对应 AF_INET,IPv6 对应 AF_INET6。
- h_length:保存IP地址长度。IPv4 的长度为 4 个字节,IPv6 的长度为 16 个字节。
- h_addr_list:这是最重要的成员。通过该成员以整数形式保存域名对应的 IP 地址。对于用户较多的服务器,可能会分配多个 IP 地址给同一域名,利用多个服务器进行均衡负载。
hostent 结构体变量的组成如下图所示
注意,gethostbyname只会搜索A记录,即IPv4地址。
从地址查找主机名
#include <netdb.h>
struct hostent *gethostbyaddr (const char *addr, socklen_t len, int family);
//返回:成功为非空指针,出错为NULL并设置h_errno
参数addr实际上不是char *类型,而是一个指向存放IPv4地址的某个in_addr结构的指针;len参数是这个结构的大小:地域IPv4地址为4,family参数为AF_INET
以上两个函数只支持IPv4,如果要支持IPv6,则要使用getaddrinfo
根据域名获取ip(支持IPv6)
getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构而不是一个地址列表,这些sockaddr结构随后可由套接字函数直使用
#include <netdb.h>
int getaddrinfo (const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result) ;
//返回:若成功为0,出错为非0
struct addrinfo {
int ai_flags; /* AI_PASSIVE, AI_CANONNAME */
int ai_family; /* AF_xxx */
int ai_socktype; /* SOCK_xxx */
int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
socklen_t ai_addrlen; /* length of ai_addr */
char *ai_canonname; /* ptr to canonical name for host */
struct sockaddr *ai_addr; /* ptr to socket address structure */
struct addrinfo *ai_next; /* ptr to next structure in linked list */
};
getaddrinfo函数的输入参数hints也为addrinfo类型。hints参数可以的一个空指针,也可以指向addrinfo结构的指针,调用者可在这个结构中填入关于期望返回的信息类型的暗示。举例来说,如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
hints结构中调用者可以设置的成员有:
- ai_flags(零个或多个或在一起的AI_xxx值)
- ai_family(某个AF_xxx值)
- ai_socktype(某个SOCK_xxx值)
- ai_protocol
其中ai_flags成员可用的标志值及其含义 例如有 AI_PASSIVE(套接字将用于被动打开),AI_CANONNAME(告知getaddrinfo函数返回主机的规范名字)等等
如果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串联起来的addrinfo结构链表。可以导致返回多个addrinfo结构的情形有以下2个:
- 如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
- 如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。
我们必须先分配一个hints结构,把它清零后填写需要的字段,再调用getaddrinfo然后遍历一个链表逐个尝试每个返回地址。
最后调用freeaddrinfo释放内存。
pjlib中addr_resolv_sock.c基本是对这两个函数的封装,其它还有3个接口在sock_common.c。
获取指定目标地址的本地地址
/**
* Get the interface IP address to send data to the specified destination.
*
* @param af The desired address family to query. Valid values
* are pj_AF_INET() or pj_AF_INET6().
* @param dst The destination host.
* @param itf_addr On successful resolution, the address family and address
* part of this socket address will be filled up with the host
* IP address, in network byte order. Other parts of the socket
* address should be ignored.
* @param allow_resolve If \a dst may contain hostname (instead of IP
* address), specify whether hostname resolution should
* be performed. If not, default interface address will
* be returned.
* @param p_dst_addr If not NULL, it will be filled with the IP address of
* the destination host.
*
* @return PJ_SUCCESS on success, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pj_getipinterface(int af,
const pj_str_t *dst,
pj_sockaddr *itf_addr,
pj_bool_t allow_resolve,
pj_sockaddr *p_dst_addr);
比如你本机有很多个地址,其中一个地址用来跟百度通讯,该接口可以根据百度的地址(目的地址),获取到对应的本地地址。
该函数创建一个socket,然后connect到目标地址,接着通过pj_sock_getsockname就可以返回此连接的本地ip,如果通过pj_sock_getpeername则获取到的就是我们传入的dst。关于UDP使用connect,其实就是限制UDP只在一个连接上收发数据,而不是每次可以指定不同地址“乱发”。
如果目标地址dst是IPv4的1.1.1.1或者是IPv6的1::1时,则获取的是默认接口(网卡)的ip,而默认接口就是默认路由的接口。关于1.1.1.1这个地址,我查了一下,大概意思是这个地址代表受限广播地址 可以作为目的地址 但不可作为源地址 ,是用来被动测试的,1.1.1.1是路由器或交换机自己设置的IP。后来被Cloudflare这个机构占了,实际上还是不太理解这个IP的使用场景。
获取主机地址
/**
* Resolve the primary IP address of local host.
*
* @param af The desired address family to query. Valid values
* are pj_AF_INET() or pj_AF_INET6().
* @param addr On successful resolution, the address family and address
* part of this socket address will be filled up with the host
* IP address, in network byte order. Other parts of the socket
* address are untouched.
*
* @return PJ_SUCCESS on success, or the appropriate error code.
*/
PJ_DECL(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr)
因为可以从多个函数获取到ip,并且一台机器可以有多网卡,每个网卡有多个ip,那么到底选择哪个ip,需要使用权重。
/* Weighting to be applied to found addresses */
WEIGHT_HOSTNAME = 1, /* hostname IP is not always valid! */
WEIGHT_DEF_ROUTE = 2,
WEIGHT_INTERFACE = 1,
WEIGHT_LOOPBACK = -5,
WEIGHT_LINK_LOCAL = -4,
WEIGHT_DISABLED = -50,
其中有3个类型的IP是负数,那些ip是不能正常用来跟其它机器通讯的,这些ip是回环、本地链路、空。hostname表示从机器名称获取的ip,Interface是从网卡获取的ip,def_route是默认路由默认网卡的ip。使用候选数组cand_addr、候选权重cand_weight、候选个数cand_cnt,三个变量来存储所有权重的ip。
1、通话pj_gethostname和pj_getaddrinfo,根据机器名获取ip。
2、通过pj_getdefaultipinterface获取给默认路由的默认网卡ip。
3、通话pj_enum_ip_interface枚举所有接口的ip。
从这三类选择权重最大的ip作为主机ip,如果找不到有效ip,则使用回环ip作为主机ip。
网络socket封装
本来sock相关的准备再写一篇,但由于没有太多有深度的东西,所以这里一笔概括。主要是在sock_bsd.c和sock_common封装了诸如socket创造,发送,接收等,和iton等工具的封装,select的封装单独在一个文件sock_select.c。