关于c:程序人生-UNIX-网络编程之-getaddrinfo-函数详解及使用举例

57次阅读

共计 9071 个字符,预计需要花费 23 分钟才能阅读完成。

本文首发于 2015-01-03 21:04:36

概述

IPv4 中应用 gethostbyname() 函数实现 主机名到地址解析,这个函数仅仅反对 IPv4,且不容许调用者指定所需地址类型的任何信息,返回的构造只蕴含了用于存储 IPv4 地址的空间。

IPv6 中引入了 getaddrinfo() 的新 API,它是协定无关的,既可用于 IPv4 也可用于 IPv6。

getaddrinfo函数可能解决 名字到地址 以及 服务到端口 这两种转换,返回的是一个 addrinfo 的构造(列表)指针而不是一个地址清单。这些 addrinfo 构造随后可由 socket 函数间接应用。

getaddrinfo函数把协定相关性平安暗藏在这个库函数外部。应用程序只有解决由 getaddrinfo 函数填写的套接口地址构造。该函数在 POSIX 标准中定义了。

函数阐明

蕴含头文件:

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

函数原型:

int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result);

参数阐明:

  • hostname: 一个主机名或者地址串(IPv4 的点分十进制串或者 IPv6 的 16 进制串)。
  • service:服务名能够是十进制的端口号,也能够是已定义的服务名称,如 ftp、http 等。
  • hints:能够是一个空指针,也能够是一个指向某个 addrinfo 构造体的指针,调用者在这个构造中填入对于冀望返回的信息类型的暗示。
  • result:本函数通过 result 指针参数返回一个指向 addrinfo 构造体链表的指针。

返回值:

0:胜利;非 0:出错。

参数设置

getaddrinfo 函数之前通常须要对以下 6 个参数进行以下设置:nodename、servname、hints 的 ai_flags、ai_family、ai_socktype、ai_protocol

在 6 项参数中,对函数影响最大的是 nodename,sernamehints.ai_flag,而 ai_family 只是有地址为 v4 地址或 v6 地址的区别。ai_protocol个别为 0 不作改变。

getaddrinfo 在理论应用中的几种罕用参数设置:

个别状况下,client/server 编程中,server 端调用 bind(如果面向连贯的还须要listen);client 则无需调用bind 函数,解析地址后间接connect(面向连贯)或间接发送数据(无连贯)。因而,比拟常见的状况有:

  1. 通常服务器端在调用 getaddrinfo 之前,ai_flags设置 AI_PASSIVE,用于bind;主机名nodename 通常会设置为 NULL,返回通配地址[::]
  2. 客户端调用 getaddrinfo 时,ai_flags个别不设置 AI_PASSIVE,然而主机名nodename 和服务名servname(更违心称之为端口)则应该不为空。
  3. 当然,即便不设置 AI_PASSIVE,取出的地址也并非不能够被 bind,很多程序中ai_flags 间接设置为 0,即 3 个标记位都不设置,这种状况下只有 hostname 和 servname 设置的没有问题就能够正确 bind。

上述情况只是简略的 client/server 中的应用,但理论在应用 getaddrinfo 和查阅国外开源代码的时候,曾遇到一些将 servname(即端口)设为 NULL 的状况(当然,此时 nodename 必不为 NULL,否则调用 getaddrinfo 会报错)。

应用须知

1)如果本函数返回胜利,那么由 result 参数指向的变量已被填入一个指针,它指向的是由其中的 ai_next 成员串联起来的 addrinfo 构造链表。

struct addrinfo 
{        
    int ai_flags; 
    int ai_family; 
    int ai_socktype; 
    int ai_protocol; 
    size_t ai_addrlen; 
    struct sockaddr *ai_addr; /* 我认为这个成员是这个函数最大的便当。*/
    char *ai_canonname; 
    struct addrinfo *ai_next; 
};

其中,sockaddr 构造体为:

在 linux 环境下,构造体 struct sockaddr 在 /usr/include/linux/socket.h 中定义,具体如下:typedef unsigned short sa_family_t;
struct sockaddr {
        sa_family_t     sa_family;    /* address family, AF_xxx       */
        char            sa_data[14];    /* 14 bytes of protocol address */
}

而且,sockaddr个别要转换为sockaddr_in

struct sockaddr_in
{ 
  short int sin_family;
  unsigned short int sin_port; 
    struct in_addr sin_addr;          
    unsigned char sin_zero[8];
}   

2)能够导致返回多个 addrinfo 构造的情景有以下 2 个:

  1. 如果与 hostname 参数关联的地址有多个,那么实用于所申请地址簇的每个地址都返回一个对应的构造。
  2. 如果 service 参数指定的服务反对多个套接口类型,那么每个套接口类型都可能返回一个对应的构造,具体取决于 hints 构造的 ai_socktype 成员。

举例来说:如果指定的服务既反对 TCP 也反对 UDP,那么调用者能够把 hints 构造中的 ai_socktype 成员设置成SOCK_DGRAM,使得返回的仅仅是实用于数据报套接口的信息。

3)咱们必须先调配一个 hints 构造,把它清零后填写须要的字段,再调用 getaddrinfo,而后遍历一个链表一一尝试每个返回地址。

4)getaddrinfo 解决了把主机名和服务名转换成套接口地址构造的问题

5)如果 getaddrinfo 出错,那么返回一个非 0 的谬误值。输入出错信息,不要用 perror,而应该用gai_strerror,该函数原型为:

const char *gai_strerror(int error);

该函数以 getaddrinfo 返回的非 0 谬误值的名字和含意为他的惟一参数,返回一个指向对应的出错信息串的指针。

6)由 getaddrinfo 返回的所有存储空间都是动静获取的,这些存储空间必须通过调用 freeaddrinfo 返回给零碎,该函数原型为:

void freeaddrinfo(struct addrinfo *ai);

ai参数应指向由 getaddrinfo 返回的第一个 addrinfo 构造。

这个链表中的所有构造以及它们指向的任何动静存储空间都被开释掉。

示例

1. 依据主机名获取 IP 地址

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
 
int main(int argc, char **argv)
{if (argc != 2) {printf("Usag: ./a.out hostname|ip\n");
        exit(1);
    }
    struct addrinfo hints;
    struct addrinfo *res, *cur;
    int ret;
    struct sockaddr_in *addr;
    char ipbuf[16];
    int port;
 
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; /* Allow IPv4 */
    hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
    hints.ai_protocol = 0; /* Any protocol */
    hints.ai_socktype = SOCK_DGRAM;
    ret = getaddrinfo(argv[1], NULL,&hints,&res);
    if (ret < 0) {fprintf(stderr, "%s\n", gai_strerror(ret));
        exit(1);
    }
    
    for (cur = res; cur != NULL; cur = cur->ai_next) {addr = (struct sockaddr_in *)cur->ai_addr;
        printf("ip: %s\n", inet_ntop(AF_INET, &addr->sin_addr, ipbuf, 16));
        printf("port: %d\n", inet_ntop(AF_INET, &addr->sin_port, (void *)&port, 2));
        //printf("port: %d\n", ntohs(addr->sin_port));
        
    }
    freeaddrinfo(res);
    exit(0);
}

2. 依据主机名和端口号获取地址信息

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <netdb.h>
#include <sys/socket.h>
void main()
{ 
    struct addrinfo *ailist, *aip; 
    struct addrinfo hint; 
    struct sockaddr_in *sinp; 
    char *hostname = "localhost";
    char buf[INET_ADDRSTRLEN]; 
    char *server = "6543"; /* 这是服务端口号 */
    const char *addr; 
    int ilRc;
    hint.ai_family = AF_UNSPEC; /* hint 的限定设置 */
    hint.ai_socktype = 0; /* 这里可是设置 socket type . 比方 SOCK——DGRAM */
    hint.ai_flags = AI_PASSIVE; /* flags 的标记很多。罕用的有 AI_CANONNAME; */
    hint.ai_protocol = 0; /* 设置协定 个别为 0,默认 */ 
    hint.ai_addrlen = 0; /* 上面不能够设置,为 0,或者为 NULL */
    hint.ai_canonname = NULL; 
    hint.ai_addr = NULL; 
    hint.ai_next = NULL;
    ilRc = getaddrinfo(hostname, server, &hint, &ailist); 
    if (ilRc < 0) 
    {printf("str_error = %s\n", gai_strerror(errno)); 
        return; 
    }
 
    /* 显示获取的信息 */
    for (aip = ailist; aip != NULL; aip = aip->ai_next)
    {sinp = (struct sockaddr_in *)aip->ai_addr;
        addr = inet_ntop(AF_INET, &sinp->sin_addr, buf, INET_ADDRSTRLEN); 
        printf("addr = %s, port = %d\n", addr?addr:"unknow", ntohs(sinp->sin_port)); 
    }
}

3. 由内核调配随机端口(再也不放心端口被占了)

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
 
#define MAX_CONN_COUNT 10
 #define INVALID_SOCKET (~0) 
 
int main(int argc, char **argv)
{
    int motionListenPort = 0;
    int motion_sock = 0;
    int    err;
    int    maxconn;
    char familyDesc[32];
    struct sockaddr_storage motion_sock_addr;
    socklen_t alen;
    struct addrinfo *addrs = NULL, *addr, hints;
    int ret;
    int tries = 0;
    
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET; /* Allow IPv4 */
    hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
    hints.ai_protocol = 0; /* Any protocol */
    hints.ai_socktype = SOCK_STREAM;
    ret = getaddrinfo(NULL, "0", &hints, &addrs);
    if (ret < 0)
    {fprintf(stderr, "%s\n", gai_strerror(ret));
        exit(1);
    }
    
    for (addr = addrs; addr != NULL; addr = addr->ai_next) {
        /* Create the socket. */
        if ((motion_sock = socket(addr->ai_family, SOCK_STREAM, 0)) == INVALID_SOCKET)
        {fprintf(stderr, "Error:could not create socket for the motion\n");
            continue;
        }
 
        /* Bind it to a kernel assigned port on localhost and get the assigned port via getsockname(). */
        if (bind(motion_sock, addr->ai_addr, addr->ai_addrlen) < 0)
        {fprintf(stderr, "Error: could not bind socket for the motion\n");
            close(motion_sock);
            motion_sock = INVALID_SOCKET;
            continue;
        }
 
        alen = sizeof(motion_sock_addr);
        if (getsockname(motion_sock, (struct sockaddr *) &(motion_sock_addr), &alen) < 0)
        {fprintf(stderr, "could not get address of socket for the motion\n");
            close(motion_sock);
            motion_sock = INVALID_SOCKET;
            continue;
        }
 
        /* Resolve the motion listen port. */
        switch(motion_sock_addr.ss_family)
        {
            case AF_INET:
                {struct sockaddr_in *motion_addr = (struct sockaddr_in *) &motion_sock_addr;
                    motionListenPort = ntohs(motion_addr->sin_port);
                    strcpy(familyDesc, "IPv4");
                    fprintf(stdout, "motionListenPort=%d, familyDesc = %s\n", motionListenPort, familyDesc);
                    break;
                }
            case AF_INET6:
                {struct sockaddr_in6 *motion_addr = (struct sockaddr_in6 *) &motion_sock_addr;
                    motionListenPort = ntohs(motion_addr->sin6_port);
                    strcpy(familyDesc, "IPv6");
                    fprintf(stdout, "motionListenPort=%d, familyDesc = %s\n", motionListenPort, familyDesc);
                    break;
                }
            default:
                {fprintf(stderr, "Error:unrecognized address family \"%d\"for the motion\n", motion_sock_addr.ss_family);
                    continue;
                }
        }
 
        /* 监听 */
        maxconn = MAX_CONN_COUNT;
        err = listen(motion_sock, maxconn);
        if (err < 0)
        {fprintf(stderr, "could not listen on socket for the motion\n");
            close(motion_sock);
            motion_sock = INVALID_SOCKET;
            continue;
        }
    }
    
    if (motion_sock == INVALID_SOCKET)
        goto listen_failed;
 
    /* XXXXXX    socket 通信过程 */
    
    
    freeaddrinfo(addrs);
    close(motion_sock);
    return 0;
    
listen_failed:
    fprintf(stderr, "Error: failed to listen for the motion\n");
    if (addrs)
        freeaddrinfo(addrs);
 
    if (motion_sock != INVALID_SOCKET)
        close(motion_sock);
    motion_sock = INVALID_SOCKET;
    return -1;
}

4. 应用 ioctl 获取指定网卡 IP 地址

#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
 
#define ETH_NAME    "eth0"
 
int main()
 
{
    int sock;
    struct sockaddr_in sin;
    struct ifreq ifr;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {perror("socket");
        return -1;        
    }
 
    strncpy(ifr.ifr_name, ETH_NAME, IFNAMSIZ);
    ifr.ifr_name[IFNAMSIZ - 1] = 0;
    if (ioctl(sock, SIOCGIFADDR, &ifr) < 0)
    {perror("ioctl");
        return -1;
    }
 
    memcpy(&sin, &ifr.ifr_addr, sizeof(sin));
    fprintf(stdout, "eth0: %s\n", inet_ntoa(sin.sin_addr));
 
    return 0;
}

5. 应用 ping 指令,依据 hostname 获取 ip 地址

本例未用 getaddrinfo,而是采纳 shell 指令办法(不举荐)。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>


// 应用 ping 指令,依据 hostname 获取 ip 地址
int getIpAddrByHostname(char *hostname, char* ip_addr, size_t ip_size)
{char command[256];
    FILE *f;
    char *ip_pos;
 
    snprintf(command, 256, "ping -c1 %s | head -n 1 | sed's/^[^(]*(\\([^)]*\\).*$/\\1/'", hostname);
    fprintf(stdout, "%s\n", command);
    if ((f = popen(command, "r")) == NULL)
    {fprintf(stderr, "could not open the command, \"%s\", %s\n", command, strerror(errno));
        return -1;
    }
 
    fgets(ip_addr, ip_size, f);
    fclose(f);
 
    ip_pos = ip_addr;
    for (;*ip_pos && *ip_pos!= '\n'; ip_pos++);
    *ip_pos = 0;
 
    return 0;
}

int main()
{char addr[64] = {0};
    getIpAddrByHostname("localhost", addr, INET_ADDRSTRLEN);
    fprintf(stdout, "localhost: %s\n", addr);
    return 0;
}

欢送关注我的微信公众号【数据库内核】:分享支流开源数据库和存储引擎相干技术。

题目 网址
GitHub https://dbkernel.github.io
知乎 https://www.zhihu.com/people/…
思否(SegmentFault) https://segmentfault.com/u/db…
掘金 https://juejin.im/user/5e9d3e…
开源中国(oschina) https://my.oschina.net/dbkernel
博客园(cnblogs) https://www.cnblogs.com/dbkernel

正文完
 0