struct addrinfo原理及操作方法總結
定義
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 */ structsockaddr *ai_addr;/* binary address */ structaddrinfo *ai_next;/* next structure in linked list */ };
各個引數以及含義可以參照ofollow,noindex">《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結構的具體記憶體分配原理,因此最簡單的方式就是閱讀原始碼,通過閱讀原始碼還可能有很多意想不到的收穫。