定义
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 结构的具体内存分配原理, 因此最简单的方式就是阅读源码, 通过阅读源码还可能有很多意想不到的收获.
来源: http://www.jianshu.com/p/df165c54d0b2