定義
addrinfo結(jié)構(gòu)主要在網(wǎng)絡(luò)編程解析hostname時(shí)使用,其在頭文件#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 */
};
各個(gè)參數(shù)以及含義可以參照《Linux下網(wǎng)絡(luò)相關(guān)結(jié)構(gòu)體 struct addrinfo》。此外,其屬性ai_addr即包含了地址信息。sockaddr類型的簡介,可以參考《sockaddr和sockaddr_in詳解》。
由于一個(gè)域名可以對應(yīng)多個(gè)IP地址,addrinfo也就支持了這個(gè)場景。addrinfo通過鏈表的方式存儲(chǔ)其他地址的,可以遍歷其屬性ai_next獲得。
相關(guān)方法
1. getaddrinfo(const char, const char, const struct addrinfo, struct addrinfo*)
該方法可參考《getaddrinfo詳解》。
2. freeaddrinfo(struct addrinfo*)
在上面介紹getaddrinfo時(shí),傳入了參數(shù)addrinfo用于保存查詢的結(jié)果。查看該方法的實(shí)現(xiàn),其在內(nèi)部調(diào)用了calloc動(dòng)態(tài)申請了內(nèi)存,并將結(jié)果保存到了傳入的參數(shù)中,因此在使用getaddrinfo成功獲取到地址后,必須要對該部分內(nèi)存進(jìn)行釋放。freeaddrinfo即是netdb.h提供的釋放內(nèi)存方法。其實(shí)現(xiàn)如下
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);
}
從其實(shí)現(xiàn)可以看出,freeaddrinfo通過循環(huán)遍歷ai_next進(jìn)行一層一層的內(nèi)存釋放。此外,通過其實(shí)現(xiàn),可以看到該方法顯示的釋放了ai_canoname屬性以及其本身,這就說明當(dāng)我們在對addrinfo進(jìn)行內(nèi)存拷貝的時(shí)候,就要注意對ai_canonname和ai_next的深拷貝的問題。
而上述邏輯中有句注釋“no need to free(ai->ai_addr)”,一開始對這個(gè)不甚理解,該屬性同樣是一個(gè)指針,為什么不需要對其指向的內(nèi)容釋放呢?經(jīng)過證明,如果不對該部分進(jìn)行深拷貝,拷貝的結(jié)果是很容易出問題的。但是如果拷貝的時(shí)候,直接顯式的調(diào)用malloc動(dòng)態(tài)申請內(nèi)存,那么在freeaddrinfo的時(shí)候就必須要顯式的調(diào)用free方法,對該部分指向的內(nèi)存進(jìn)行釋放,否則必然會(huì)造成內(nèi)存泄漏。為了搞清楚這個(gè)問題,必須要深入getaddrinfo方法找到系統(tǒng)對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;
}
}
通過上述實(shí)現(xiàn)可以發(fā)現(xiàn),原來addrinfo在申請地址的時(shí)候直接申請的是addrinfo大小外加一個(gè)sockaddr的大小,其屬性ai_addr所指向的地址內(nèi)容是緊跟在addrinfo 結(jié)構(gòu)后面的,因此在對其賦值的時(shí)候是直接ai->ai_addr = (struct sockaddr*)(ai + 1);這也就解釋了為什么在freeaddrinfo的時(shí)候沒有顯式的調(diào)用free(ai_addr),其在free(ai)的時(shí)候就同步釋放了。
3. 拷貝addrinfo結(jié)構(gòu)
在netdb.h中并沒有提供拷貝addrinfo結(jié)構(gòu)的方法,因此這個(gè)方法需要自己實(shí)現(xiàn)。下面是我的一個(gè)實(shí)現(xiàn)方案,僅供參考。
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;
}
總結(jié)
在實(shí)現(xiàn)拷貝方法的時(shí)候,主要的精力花在了ai_addr屬性的處理上。由于并沒有找到資料介紹addrinfo結(jié)構(gòu)的具體內(nèi)存分配原理,因此最簡單的方式就是閱讀源碼,通過閱讀源碼還可能有很多意想不到的收獲。