美文网首页程序员
struct addrinfo原理及操作方法总结

struct addrinfo原理及操作方法总结

作者: a421358a9f56 | 来源:发表于2018-09-20 20:12 被阅读2次

    定义

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

    相关文章

      网友评论

        本文标题:struct addrinfo原理及操作方法总结

        本文链接:https://www.haomeiwen.com/subject/bwgnnftx.html