本文首发于 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;}

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

题目网址
GitHubhttps://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