• socket网络信息查询API


    socket网络信息查询API

    学习《Linux高性能服务器编程》第五章Linux网络编程基础API,为了印象深刻一些,多动手多实践,所以记下这个笔记。这一篇主要记录Linux中socket网络信息查询API,包括gethostbyname和gethostbyaddr、getservbyname和getservbyport、getaddrinfo、getnameinfo。

    socket当中两要素:IP和端口号,都是用数值表示的。但是有时候我们可以使用主机名代替IP,使用服务名代替端口号。

    telnet 127.0.0.1 80
    telnet localhost www
    
    • 1
    • 2

    这个功能就是使用网络信息API实现的。

    gethostbyname和gethostbyaddr

    gethostbyname函数根据主机名称获取主机的完整信息,gethostbyaddr函数根据IP地址获取主机的完整信息。gethostbyname函数通常先在本地的/etc/hosts配置文件中查找主机,如果没有找到,再去访问DNS服务器。这两个函数的定义如下:

    #include 
    extern int h_errno;
    
    struct hostent *gethostbyname(const char *name);
    
    #include        /* for AF_INET */
    struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    name参数表示目标主机的主机名。

    addr参数指定目标主机的IP地址,len参数指定addr的所指定IP的长度

    type参数指定IP地址的类型,比如AF_INET

    其中hostent定义如下:

    #include 
    
    struct hostent {
       char  *h_name;            /* official name of host */
       char **h_aliases;         /* alias list */
       int    h_addrtype;        /* host address type */
       int    h_length;          /* length of address */
       char **h_addr_list;       /* list of addresses */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    参数介绍

    h_name :主机名
    h_aliases:主机别名列表,可能有多个
    h_addrtype:地址类型(地址族)
    h_length:地址长度
    h_addr_list:按网络字节序列出的主机IP地址列表

    从网上找了个图显示了一下

    img

    gethostbyname举例

    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char **argv)
    {
        if (argc != 2)
        {
            printf("Use example: %s www.baidu.com\n", *argv);
            return -1;
        }
    
        char *name = argv[1];
        struct hostent *hptr;
    
        hptr = gethostbyname(name);
        if (hptr == NULL)
        {
            printf("gethostbyname error for host: %s: %s\n", name, hstrerror(h_errno));
            return -1;
        }
        //输出主机名
        printf("\tofficial: %s\n", hptr->h_name);
    
        //输出主机的别名
        char **pptr;
        char str[INET_ADDRSTRLEN];
        for (pptr = hptr->h_aliases; *pptr != NULL; pptr++)
        {
            printf("\talias: %s\n", *pptr);
        }
    
        //输出ip地址
        switch (hptr->h_addrtype)
        {
        case AF_INET:
            pptr = hptr->h_addr_list;
            for (; *pptr != NULL; pptr++)
            {
                printf("\taddress: %s\n",
                       inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
            }
            break;
        default:
            printf("unknown address type\n");
            break;
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    image-20220814105118617

    gethostbyaddr举例

    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char **argv)
    {
        if (argc != 2)
        {
            printf("Use example: %s 127.0.0.1\n", *argv);
            return -1;
        }
    
        char *ip = argv[1];
        struct in_addr addr;
    
        inet_pton(AF_INET, ip, &addr);
        struct hostent *hptr;
    
        hptr = gethostbyaddr(&addr, sizeof(addr), AF_INET);
        if (hptr == NULL)
        {
            printf("gethostbyaddr error for host: %s: %s\n", ip, hstrerror(h_errno));
            return -1;
        }
        //输出主机名
        printf("\tofficial: %s\n", hptr->h_name);
    
        //输出主机的别名
        char **pptr;
        char str[INET_ADDRSTRLEN];
        for (pptr = hptr->h_aliases; *pptr != NULL; pptr++)
        {
            printf("\talias: %s\n", *pptr);
        }
    
        //输出ip地址
        switch (hptr->h_addrtype)
        {
        case AF_INET:
            pptr = hptr->h_addr_list;
            for (; *pptr != NULL; pptr++)
            {
                printf("\taddress: %s\n",
                       inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
            }
            break;
        default:
            printf("unknown address type\n");
            break;
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    image-20220814105222471

    getservbyname和getservbyport

    getservbyname函数根据名称获取某个服务的完整信息,getservbyport函数根据端口号获取某个服务的完整信息。它们实际上都是通过读取/etc/services文件来获取服务的信息的。这两个函数的定义如下:

    #include 
    
    struct servent *getservbyname(const char *name, const char *proto);
    struct servent *getservbyport(int port, const char *proto);
    
    • 1
    • 2
    • 3
    • 4

    name参数指定目标服务的名字。

    port参数指定目标服务对应的端口号。

    proto参数指定服务类型,给它传递“tcp”表示获取流服务,给它传递“udp”表示获取数据报服务,给它传递NULL则表示获取所有类型的服务。

    函数返回的servent的定义如下:

    #include 
    struct servent {
       char  *s_name;       /* official service name */
       char **s_aliases;    /* alias list */
       int    s_port;       /* port number */
       char  *s_proto;      /* protocol to use */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    s_name:服务名称

    s_aliases:服务别名列表,可能有多个

    s_port:端口号

    s_proto:服务类型,通常是tcp或者udp

    getservbyname举例

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char const *argv[])
    {
    
        struct servent *servinfo = getservbyname("ssh", "tcp");
        assert(servinfo);
        printf("name is %s\n", servinfo->s_name);
    
        char **pptr;
        for (pptr = servinfo->s_aliases; *pptr != NULL; pptr++)
        {
            printf("alias: %s\n", *pptr);
        }
        printf("port is %d\n", ntohs(servinfo->s_port));
        printf("protocol is %s\n", servinfo->s_proto);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    image-20220814141312451

    getservbyport举例

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char const *argv[])
    {
    
        int port = 80;
        struct servent *servinfo = getservbyport(htons(port), "tcp");
        assert(servinfo);
        printf("name is %s\n", servinfo->s_name);
    
        char **pptr;
        for (pptr = servinfo->s_aliases; *pptr != NULL; pptr++)
        {
            printf("alias: %s\n", *pptr);
        }
        printf("port is %d\n", ntohs(servinfo->s_port));
        printf("protocol is %s\n", servinfo->s_proto);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    image-20220814142922859

    需要指出的是,上面讨论的4个函数都是不可重入的,即非线程安全的。不过netdb.h头文件给出了它们的可重入版本。正如Linux下所有其他函数的可重入版本的命名规则那样,这些函数的函数名是在原函数名尾部加上_r (re-entrant)

    getaddrinfo

    getaddrinfo函数既能通过主机名获得IP地址(内部使用的是gethostbyname函数),也能通过服务名获得端口号(内部使用的是getservbyname函数)。它是否可重人取决于其内部调用的gethostbynamegetservbyname函数是否是它们的可重入版本。该函数的定义如下:

    #include 
    #include 
    #include 
    
    int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    node参数可以接收主机名,也可以接收字符串表示的IP地址,用点分十分制。

    service参数可以接收服务名,也可以接收字符串表示的十进制端口。

    hints参数是给getaddrinfo的一个提示,以对getaddrinfo的输出进行更精确的控制。hints参数可以设置为NULL,表示允许getaddrinfo反馈任何可用的结果。

    res参数返回一个链表,这个链表用于存储getaddrinfo反馈的结果。

    除此之外,在我们调用完getaddrinfo之后,需要使用freeaddrinfo对res进行内存释放。

    #include 
    #include 
    #include 
    void freeaddrinfo(struct addrinfo *res);
    
    • 1
    • 2
    • 3
    • 4

    addrinfo的定义如下

    struct addrinfo {
       int              ai_flags;
       int              ai_family;
       int              ai_socktype;
       int              ai_protocol;
       socklen_t        ai_addrlen;
       struct sockaddr *ai_addr;
       char            *ai_canonname;
       struct addrinfo *ai_next;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ai_family:地址族,比如:AF_INET

    ai_socktype:服务类型,比如:SOCK_STREAM

    ai_protocol:指具体的网络协议

    ai_addrlen:地址ai_addr的长度

    ai_addr:指向socket的地址

    ai_canonname:主机的别名

    ai_next:链表的下一个对象

    ai_flags可以取下表中标志

    image-20220814160248251

    当我们使用hints参数的时候,可以设置其ai_flagsai_familyai_socktype ai_protocol四个字段,其他字段则必须被设置为NULL。

    根据主机名获取IP地址:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char **argv)
    {
        if (argc != 2)
        {
            printf("Use example: %s www.baidu.com\n", *argv);
            return -1;
        }
    
        char *name = argv[1];
        struct addrinfo hints;
        struct addrinfo *res, *cur;
        int ret;
        struct sockaddr_in *addr;
        char ipbuf[16];
    
        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(name, NULL, &hints, &res);
        assert(ret >= 0);
    
        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, cur->ai_addrlen));
            printf("alias: %s\n", cur->ai_canonname);
        }
        freeaddrinfo(res);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    image-20220814165923059
    不过不知道为啥别名为null

    根据主机名和端口号获取地址信息:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char **argv)
    {
        if (argc != 2)
        {
            printf("Use example: %s 80\n", *argv);
            return -1;
        }
    
        char *port = argv[1];
        char *hostname = "localhost";
        struct addrinfo hints;
        struct addrinfo *res, *cur;
        int ret;
        struct sockaddr_in *addr;
        char ipbuf[16];
    
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC; /* Allow IPv4 */
        hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
        hints.ai_protocol = 0;       /* Any protocol */
        hints.ai_socktype = 0;
    
        ret = getaddrinfo(hostname, port, &hints, &res);
    
        assert(ret >= 0);
    
        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, cur->ai_addrlen));
            printf("port: %d\n", ntohs(addr->sin_port));
            printf("alias: %s\n", cur->ai_canonname);
        }
        freeaddrinfo(res);
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    image-20220814170106763

    不过不知道为啥别名为null

    getnameinfo

    getnameinfo函数能通过socket地址同时获得以字符串表示的主机名(内部使用的是gethostbyaddr函数)和服务名(内部使用的是getservbyport函数)。它是否可重入取决于其内部调用的gethostbyaddr和 getservbyport函数是否是它们的可重入版本。该函数的定义如下:

    #include 
    #include 
    
    int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,char *host, socklen_t hostlen,
                    char *serv, socklen_t servlen, int flags);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    getnameinfo将返回的主机名存储在host参数指向的缓存中,将服务名存储在serv参数指向的缓存中,hostlenservlen参数分别指定这两块缓存的长度。flags参数控制getnameinfo的行为,它可以接收下表中的选项。

    image-20220814171842523
    getaddrinfogetnameinfo函数成功时返回0,失败则返回错误码。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char **argv)
    {
        if (argc != 3)
        {
            printf("Use example: %s 127.0.0.1 80\n", *argv);
            return -1;
        }
    
        char *ip = argv[1];
        int port = atoi(argv[2]);
        char hostname[128] = {0};
        char servername[128] = {0};
        struct sockaddr_in addr_dst;
        memset(&addr_dst, 0, sizeof(addr_dst));
        addr_dst.sin_family = AF_INET;
        addr_dst.sin_addr.s_addr = inet_addr(ip);
        addr_dst.sin_port = htons(port);
    
        int ret = getnameinfo((struct sockaddr *)&addr_dst, sizeof(addr_dst), hostname, sizeof(hostname), servername, sizeof(servername), 0);
        assert(ret == 0);
        printf("hostname IP: %s \n", hostname);
        printf("servername : %s \n", servername);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    image-20220814181037933

  • 相关阅读:
    LM小型可编程控制器软件(基于CoDeSys)笔记三十一:软件安装流程
    vue-json-editor
    SpringBoot多线程环境下,解决多个定时器冲突问题(荣耀典藏版)
    PyTorch - autograd自动微分
    网络套接字(Udp实现简单的网络通信)
    软件测试的基础知识
    shell-运算符
    java 泛型----T、?的使用
    yolov5 应用整理
    枚举类和集合
  • 原文地址:https://blog.csdn.net/qq_41474648/article/details/126335844