struct addrinfo原理及操作方法总结

定义

addrinfo结构主要在网络编程解析hostname时使用,其在头文件#include中,定义如下:

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结构的具体内存分配原理,因此最简单的方式就是阅读源码,通过阅读源码还可能有很多意想不到的收获。

你可能感兴趣的:(struct addrinfo原理及操作方法总结)