网络编程addrinfo结构体与getaddrinfo函数

网络编程addrinfo结构体与getaddrinfo函数
addrinfo结构体的定义如下:

struct addrinfo
{
     int ai_flags; /* customize behavior */
     int ai_family; /* address family */
     int ai_socktype; /* socket type */
     int ai_protocol; /* protocol */
     socklen_t ai_addrlen; /* length in bytes of address */
     struct sockaddr *ai_addr; /* address */
     char *ai_canonname; /* canonical name of host */
     struct addrinfo *ai_next; /* next in list */

};


ai_family指定了地址族,可取值如下:
AF_INET          2            IPv4
AF_INET6        23            IPv6
AF_UNSPEC        0            协议无关
ai_socktype指定我套接字的类型
SOCK_STREAM        1            流
SOCK_DGRAM        2            数据报

在AF_INET通信域中套接字类型SOCK_STREAM的默认协议是TCP(传输控制协议)
在AF_INET通信域中套接字类型SOCK_DGRAM的默认协议是UDP(用户数据报协议)

ai_protocol指定协议类型。可取的值取决于ai_address和ai_socktype的值

ai_flags指定了如何来处理地址和名字,可取值如下:


getaddrinfo()函数

通过res返回一个指向struct addrinfo结构链表的指针(注意这里返回的是一个链表),如果调用getaddrinfo后没有释放内存,会造成内存泄漏。

定义及需要的头文件如下:

#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *restrict nodename, /* host 或者IP地址 */
    const char *restrict servname, /* 十进制端口号 或者常用服务名称如"ftp"、"http"等 */
    const struct addrinfo *restrict hints, /* 获取信息要求设置 */
    struct addrinfo **restrict res); /* 获取信息结果 */

void freeaddrinfo(struct addrinfo *ai);

1) nodename

主机名("www.baidu.com")或者是数字化的地址字符串(IPv4的点分十进制串("192.168.1.100")或者IPv6的16进制串("2000::1:2345:6789:abcd")),

如果 ai_flags 中设置了AI_NUMERICHOST 标志,那么该参数只能是数字化的地址字符串,不能是域名,

该标志的作用就是阻止进行域名解析。

nodename 和 servname 可以设置为NULL,但是同时只能有一个为NUL。

2) servname

服务名可以是十进制的端口号("8080")字符串,也可以是已定义的服务名称,如"ftp"、"http"等,详细请查看/etc/services 文件,

最后翻译成对应服务的端口号。如果此参数设置为NULL,那么返回的socket地址中的端口号不会被设置。

如果 ai_flags 设置了AI_NUMERICSERV 标志并且该参数未设置为NULL,那么该参数必须是一个指向10进制的端口号字符串,

不能设定成服务名,该标志就是用来阻止服务名解析。

3) hints

该参数指向用户设定的 struct addrinfo 结构体,只能设定该结构体中 ai_family、ai_socktype、ai_protocol 和 ai_flags 四个域,其他域必须设置为0 或者 NULL, 通常是申请 结构体变量后使用memset()初始化再设定指定的四个域。

该参数可以设置为NULL,等价于 ai_socktype = 0, ai_protocol = 0,ai_family = AF_UNSPEC, ai_flags = 0 (在GNU Linux中ai_flag = AI_V4MAPPED | AI_ADDRCONFIG,可以看作是GNU的改进)。

 ① ai_family

  指定返回地址的协议簇,取值范围:AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNSPEC(IPv4 and IPv6)

   ② ai_socktype

  具体类型请查看struct addrinfo 中的 enum __socket_type 类型,用于设定返回地址的socket类型,

  常用的有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW, 设置为0表示所有类型都可以。

 ③ ai_protocol

  具体取值范围请查看 Ip Protocol ,常用的有 IPPROTO_TCP、IPPROTO_UDP 等,设置为0表示所有协议。

 ④ ai_flags

  附加选项,多个选项可以使用或操作进行结合,具体取值范围请查看struct addrinfo , 常用的标志如下:

  • AI_PASSIVE

    如果设置了 AI_PASSIVE 标志,并且 nodename 是 NULL, 那么返回的socket地址可以用于的bind()函数,

       返回的地址是通配符地址(wildcard address, IPv4时是INADDR_ANY,IPv6时是IN6ADDR_ANY_INIT),

       这样应用程序(典型是server)就可以使用这个通配符地址用来接收任何请求主机地址的连接,

       如果 nodename 不是NULL,那么 AI_PASSIVE 标志被忽略;

    如果未设置AI_PASSIVE标志,返回的socket地址可以用于connect(), sendto(), 或者 sendmsg()函数。

    如果 nodename 是NULL,那么网络地址会被设置为lookback接口地址(IPv4时是INADDR_LOOPBACK,IPv6时是IN6ADDR_LOOPBACK_INIT),

    这种情况下,应用是想与运行在同一个主机上另一个应用通信。

  • AI_CANONNAME

     请求canonical(主机的official name)名字。如果设置了该标志,那么 res 返回的第一个 struct addrinfo 中的 ai_canonname 域会存储official name的指针。

  • AI_NUMERICHOST

     阻止域名解析,具体见 nodename 中的说明。

  • AI_NUMERICSERV

    阻止服务名解析,具体见 servname 中的说明。

  • AI_V4MAPPED

     当 ai_family 指定为AF_INT6(IPv6)时,如果没有找到IPv6地址,那么会返回IPv4-mapped IPv6 地址,

    也就是说如果没有找到AAAA record(用来将域名解析到IPv6地址的DNS记录),那么就查询A record(IPv4),

    将找到的IPv4地址映射到IPv6地址, IPv4-mapped IPv6 地址其实是IPv6内嵌IPv4的一种方式,

    地址的形式为"0::FFFF:a.b.c.d",例如"::ffff:192.168.89.9"(混合格式)这个地址仍然是一个IPv6地址,

           只是"0000:0000:0000:0000:0000:ffff:c0a8:5909"(16机制格式)的另外一种写法罢了。

    当 ai_family 不是AF_INT6(IPv6)时,该标志被忽略。

  • AI_ALL

     查询IPv4和IPv6地址

  • AI_ADDRCONFIG

    只有当主机配置了IPv4地址才进行查询IPv4地址;只有当主机配置了IPv6地址才进行查询IPv6地址.

4) res

 该参数获取一个指向存储结果的 struct addrinfo 结构体列表,使用完成后调用 freeaddrinfo() 释放存储结果空间。

 

Returns: 0 if OK, nonzero error code on error

void freeaddrinfo(struct addrinfo *ai);

由getaddrinfo返回的存储空间,包括addrinfo结构、ai_addr结构和ai_canonname字符串,都是用malloc动态获取的。这些空间可调用 freeaddrinfo释放。其原型如下:
void freeaddrinfo (struct addrinfo*ai);
ai指向getaddrinfo返回的第一个addrinfo结构。在该链表中的所有结构,以及这些结构所指向的动态存储空间都被释放。
假设我们调用getaddrinfo,顺着addrinfo结构链表找到所需的结构,然后只复制该addrinfo结构以保存其信息,再调用 freeaddrinfo,就会产生一个潜藏的错误。原因是addrinfo结构中的指针指向动态分配的内存。
因此由我们保存的结构指向的内存在调用 freeaddrinfo后就释放,可能将作它用。只复制addrinfo结构,而不复制addrinfo结构所指向的其他结构,叫做浅拷贝或浅复制。
复制addrinfo结构,同时复制addrinfo结构所指向的其他结构,称为深拷贝或深复制。

getaddrinfo函数允许将一个主机名字和服务名字映射到一个地址。
使用示例如下:
#include<stdio.h>
#include<stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>

void print_family(struct addrinfo *aip)
{
    printf(" family ");
    switch (aip->ai_family) {
    case AF_INET:
        printf("inet");
        break;
    case AF_INET6:
        printf("inet6");
        break;
    case AF_UNIX:
        printf("unix");
        break;
    case AF_UNSPEC:
        printf("unspecified");
        break;
    default:
        printf("unknown");
    }
}

void print_type(struct addrinfo *aip)
{
    printf(" type ");
    switch (aip->ai_socktype) 
    {
    case SOCK_STREAM:
        printf("stream");
        break;
    case SOCK_DGRAM:
        printf("datagram");
        break;
    case SOCK_SEQPACKET:
        printf("seqpacket");
        break;
    case SOCK_RAW:
        printf("raw");
        break;
    default:
        printf("unknown (%d)", aip->ai_socktype);
    }
}

void print_protocol(struct addrinfo *aip)
{
    printf(" protocol ");
    switch (aip->ai_protocol) 
    {
    case 0:
        printf("default");
        break;
    case IPPROTO_TCP:
        printf("TCP");
        break;
    case IPPROTO_UDP:
        printf("UDP");
        break;
    case IPPROTO_RAW:
        printf("raw");
        break;
    default:
        printf("unknown (%d)", aip->ai_protocol);
    }
}

void print_flags(struct addrinfo *aip)
{
    printf("flags");
    if (aip->ai_flags == 0) 
    {
        printf(" 0");
    } 
    else 
    {
        if (aip->ai_flags & AI_PASSIVE)
            printf(" passive");
        if (aip->ai_flags & AI_CANONNAME)
            printf(" canon");
        if (aip->ai_flags & AI_NUMERICHOST)
            printf(" numhost");
#if defined(AI_NUMERICSERV)
        if (aip->ai_flags & AI_NUMERICSERV)
            printf(" numserv");
#endif
#if defined(AI_V4MAPPED)
        if (aip->ai_flags & AI_V4MAPPED)
            printf(" v4mapped");
#endif
#if defined(AI_ALL)
        if (aip->ai_flags & AI_ALL)
            printf(" all");
#endif
    }
}

int main(int argc, char *argv[])
{
    struct addrinfo        *ailist, *aip;
    struct addrinfo        hint;
    struct sockaddr_in    *sinp;
    const char             *addr;
    int                 err;
    char                 abuf[INET_ADDRSTRLEN];

    if (argc != 3)
        printf("usage: %s nodename service", argv[0]);
    hint.ai_flags = AI_CANONNAME;
    hint.ai_family = 0;
    hint.ai_socktype = 0;
    hint.ai_protocol = 0;
    hint.ai_addrlen = 0;
    hint.ai_canonname = NULL;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)
        printf("getaddrinfo error: %s", gai_strerror(err));
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        print_flags(aip);
        print_family(aip);
        print_type(aip);
        print_protocol(aip);
        printf("\n\thost %s", aip->ai_canonname?aip->ai_canonname:"-");
        if (aip->ai_family == AF_INET) {
            sinp = (struct sockaddr_in *)aip->ai_addr;
            addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf,INET_ADDRSTRLEN);
            printf(" address %s", addr?addr:"unknown");
            printf(" port %d", ntohs(sinp->sin_port));
        }
        printf("\n");
    }
    exit(0);
}
代码说明:sinp = (struct sockaddr_in *)aip->ai_addr;会将struct sockaddr 变量强制转化为struct sockaddr_in 类型
inet_ntop函数用于在二进制格式与点分十进制格式表示(a.b.c.d)之间进行转换
执行结果:#./a X86-PC nfs
root@X86-PC:/home/cheney/work-link/linux_C_Test/linux-program/fig16# ./a X86-PC nfs
flags canon family inet type stream protocol TCP
host X86-PC address 127.0.1.1 port 2049
flags canon family inet type datagram protocol UDP
host - address 127.0.1.1 port 2049
root@X86-PC:/home/cheney/work-link/linux_C_Test/linux-program/fig16#

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值