定义
addrinfo结构主要在网络编程解析hostname时使用,其在头文件#include<netdb.h>中,定义如下:
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 */
socklen_t ai_addrlen; /* length of ai_addr */
char *ai_canonname; /* canonical name for hostname */
struct sockaddr *ai_addr; /* binary address */
struct addrinfo *ai_next; /* next structure in linked list */
};
各个参数以及含义可以参照《Linux下网络相关结构体 struct addrinfo》。此外,其属性ai_addr即包含了地址信息。sockaddr类型的简介,可以参考《sockaddr和sockaddr_in详解》。
由于一个域名可以对应多个IP地址,addrinfo也就支持了这个场景。addrinfo通过链表的方式存储其他地址的,可以遍历其属性ai_next获得。
相关方法
1. getaddrinfo(const char, const char, const struct addrinfo, struct addrinfo*)
该方法可参考《getaddrinfo详解》。
2. freeaddrinfo(struct addrinfo*)
在上面介绍getaddrinfo时,传入了参数addrinfo用于保存查询的结果。查看该方法的实现,其在内部调用了calloc动态申请了内存,并将结果保存到了传入的参数中,因此在使用getaddrinfo成功获取到地址后,必须要对该部分内存进行释放。freeaddrinfo即是netdb.h提供的释放内存方法。其实现如下
void freeaddrinfo(struct addrinfo *ai)
{
struct addrinfo *next;
#if defined(__BIONIC__)
if (ai == NULL) return;
#else
_DIAGASSERT(ai != NULL);
#endif
do {
next = ai->ai_next;
if (ai->ai_canonname)
free(ai->ai_canonname);
/* no need to free(ai->ai_addr) */
free(ai);
ai = next;
} while (ai);
}
从其实现可以看出,freeaddrinfo通过循环遍历ai_next进行一层一层的内存释放。此外,通过其实现,可以看到该方法显示的释放了ai_canoname属性以及其本身,这就说明当我们在对addrinfo进行内存拷贝的时候,就要注意对ai_canonname和ai_next的深拷贝的问题。
而上述逻辑中有句注释“no need to free(ai->ai_addr)”,一开始对这个不甚理解,该属性同样是一个指针,为什么不需要对其指向的内容释放呢?经过证明,如果不对该部分进行深拷贝,拷贝的结果是很容易出问题的。但是如果拷贝的时候,直接显式的调用malloc动态申请内存,那么在freeaddrinfo的时候就必须要显式的调用free方法,对该部分指向的内存进行释放,否则必然会造成内存泄漏。为了搞清楚这个问题,必须要深入getaddrinfo方法找到系统对addrinfo赋值的地方。
//bionic/libc/dns/net/getaddrinfo.c
static int android_getaddrinfo_proxy(
const char *hostname, const char *servname,
const struct addrinfo *hints, struct addrinfo **res, unsigned netid)
{
……
while (1) {
struct addrinfo* ai = calloc(1, sizeof(struct addrinfo) + sizeof(struct sockaddr_storage));
if (ai == NULL) {
break;
}
ai->ai_addr = (struct sockaddr*)(ai + 1);
……
ai->ai_canonname = (char*) malloc(name_len);
……
*nextres = ai;
nextres = &ai->ai_next;
}
}
通过上述实现可以发现,原来addrinfo在申请地址的时候直接申请的是addrinfo大小外加一个sockaddr的大小,其属性ai_addr所指向的地址内容是紧跟在addrinfo 结构后面的,因此在对其赋值的时候是直接ai->ai_addr = (struct sockaddr*)(ai + 1);这也就解释了为什么在freeaddrinfo的时候没有显式的调用free(ai_addr),其在free(ai)的时候就同步释放了。
3. 拷贝addrinfo结构
在netdb.h中并没有提供拷贝addrinfo结构的方法,因此这个方法需要自己实现。下面是我的一个实现方案,仅供参考。
int dumpAddrInfo(struct addrinfo **dst, struct addrinfo *src) {
if (src == NULL) return -1;
int ret = 0;
struct addrinfo *aiDst = NULL, *aiSrc = src, *aiCur = NULL;
while(aiSrc) {
size_t aiSize = sizeof(struct addrinfo) + sizeof(struct sockaddr_storage);
struct addrinfo* ai = (struct addrinfo*) calloc(1, aiSize);
if (ai == NULL) {
ret = -1;
break;
}
memcpy(ai, aiSrc, aiSize);
ai->ai_addr = (struct sockaddr*)(ai + 1);
ai->ai_next = NULL;
if (aiSrc->ai_canonname != NULL) {
ai->ai_canonname = strdup(aiSrc->ai_canonname);
}
if (aiDst == NULL) {
aiDst = ai;
} else {
aiCur->ai_next = ai;
}
aiCur = ai;
aiSrc = aiSrc->ai_next;
}
if (ret) {
freeaddrinfo(aiDst);
return ret;
}
*dst = aiDst;
return ret;
}
总结
在实现拷贝方法的时候,主要的精力花在了ai_addr属性的处理上。由于并没有找到资料介绍addrinfo结构的具体内存分配原理,因此最简单的方式就是阅读源码,通过阅读源码还可能有很多意想不到的收获。