struct addrinfo原理及操作方法總結(jié)

定義

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)存分配原理,因此最簡單的方式就是閱讀源碼,通過閱讀源碼還可能有很多意想不到的收獲。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、C語言基礎(chǔ) 1、struct 的內(nèi)存對齊和填充問題其實(shí)只要記住一個(gè)概念和三個(gè)原則就可以了: 一個(gè)概念:自然對齊...
    XDgbh閱讀 2,348評(píng)論 1 38
  • 一、Linux系統(tǒng)概述 不加引號(hào)可理解為宏,直接替換,單引號(hào)中特殊字符會(huì)被解釋為普通字符,雙引號(hào)中$,,'還是特殊...
    赤果_b4a7閱讀 1,635評(píng)論 0 2
  • 研究IPv6 socket編程原因: Supporting IPv6 in iOS 9 WWDC2015蘋果宣布在...
    li大鵬閱讀 7,637評(píng)論 7 15
  • 大綱 一.Socket簡介 二.BSD Socket編程準(zhǔn)備 1.地址 2.端口 3.網(wǎng)絡(luò)字節(jié)序 4.半相關(guān)與全相...
    VD2012閱讀 2,705評(píng)論 0 5
  • 我們每天走路不知道要踩死多少只螞蟻,螞蟻的生命在我們?nèi)祟愂澜缋锩煨〉牟恢狄惶?。我們也從來沒想過,踩死一只螞蟻會(huì)對它...
    遇見橙子閱讀 245評(píng)論 0 0

友情鏈接更多精彩內(nèi)容