• 《Unix 网络编程》11:名字和地址转换


    名字和地址转换

    系列文章导航:《Unix 网络编程》笔记

    域名系统

    简介

    域名系统主要用于主机名字和 IP 地址之间的映射。主机名可以是:

    • 简单名字,如:centos01
    • 全限定域名(FQDN[1]),如:xxx.com

    资源记录

    记录 作用
    A 指向IPv4
    AAAA 指向IPv6
    PTR 把IP地址映射为主机名
    MX 邮件记录
    CNAME 为二级域名指定域名或IP

    解析器和名字服务器

    DNS

    DNS替代方法

    如果使用 DNS 查找主机名,则使用 /etc/resolv.conf 指定的 DNS

    有如下替代方法:

    • 静态主机文件,如 /etc/hosts
    • 网络信息系统(NIS)
    • 轻权目录访问协议(LDAP)

    所有这些差异对应用开发人员是透明的,我们只需调用相关的解析器函数即可

    IPv4 函数学习

    域名和地址转换

    gethostbyname

    执行对 A 记录的查询,返回 IPv4 地址:

    #include <netdb.h>
    
    struct hostent *gethostbyname(const char * hostname);
    
    // hostent:
    struct hostent {
      char *h_name;	// 正式主机名
      char **h_aliases; // 别名s
      int h_addrtype; // AF_INET
      int h_length; // 4 (32位IP地址)
      char **h_addr_list; // IP地址s
    }
    

    错误情况

    发生错误时,不设置 errno 变量,而是将全局整型变量 h_errno 设置为在头文件 netdb.h 中定义的如下常量之一:

    • HOST_NOT_FOUND
    • TRY_AGAIN
    • NO_RECOVERY
    • NO_DATA(等同于 NO_ADDRESS):表明主机在,但是没有 A 记录

    多数解析器提供名为 hstrerror 函数,可以将某个 h_errno 代表的具体错误信息返回

    案例

    #include "unp.h"
    
    int main(int argc, char **argv)
    {
        char *ptr, **pptr;
        char str[INET_ADDRSTRLEN];
        struct hostent *hptr;
    
        while (--argc > 0)
        {
            // 遍历每一个域名
            ptr = *++argv;
            if ((hptr = gethostbyname(ptr)) == NULL)
            {
                // 错误信息
                err_msg("gethostbyname error for host: %s: %s",
                        ptr, hstrerror(h_errno));
                continue;
            }
    
            // 各种打印
            printf("official hostname: %s\n", hptr->h_name);
    
            for (pptr = hptr->h_aliases; *pptr != NULL; pptr++)
                printf("\talias: %s\n", *pptr);
    
            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:
                err_ret("unknown address type");
                break;
            }
        }
        exit(0);
    }
    
    [root@centos-5610 names]# ./hostent ethy.cn www.ethy.cn smtp.ethy.cn mail.ethy.cn
    official hostname: ym.163.com
            alias: ethy.cn
            address: 117.147.199.37
    gethostbyname error for host: www.ethy.cn: Unknown host
    official hostname: cli.ym.ntes53.netease.com
            alias: smtp.ethy.cn
            alias: smtp.ym.163.com
            address: 101.71.155.42
    

    gethostbyaddr

    与上一个的功能正好相反,查询 PTR 记录

    #inlcude <netdh.h>
    
    struct hostent *gethostbyaddr(const char *addr, 
                                  socklen_t len, // 对于 IPv4 为4
                                  int family); // AF_INET
    

    服务和端口转换

    /etc/services 文件中保存了许多知名服务的端口和服务名称的映射,如下:

     time            37/tcp          timserver
     time            37/udp          timserver
     rlp             39/tcp          resource        # resource location
     rlp             39/udp          resource        # resource location
     nameserver      42/tcp          name            # IEN 116
     nameserver      42/udp          name            # IEN 116
     nicname         43/tcp          whois
     nicname         43/udp          whois
     tacacs          49/tcp                          # Login Host Protocol (TACACS)
     tacacs          49/udp                          # Login Host Protocol (TACACS)
     re-mail-ck      50/tcp                          # Remote Mail Checking Protocol
     re-mail-ck      50/udp                          # Remote Mail Checking Protocol
     domain          53/tcp                          # name-domain server
     domain          53/udp
     whois++         63/tcp          whoispp
     whois++         63/udp          whoispp
    

    getservbyname

    #include <netdb.h>
    
    struct servent *getservbyname(const char* servname, const char *protoname);
    
    
    // servent
    struct servent {
      char *s_name;
      char **s_aliases;
      int s_port;
      char *s_proto;
    }
    

    几个案例:

    getservbyname("domain", "udp");
    getservbyname("ftp", "tcp");
    getservbyname("ftp", NULL);
    getesrvbyname("ftp", "udp");
    

    如果没有指定协议,则会自动匹配(一般来说同一服务的 TCP 和 UDP 端口是相同的),但是如果指定的协议没有,则会报错

    getservbyport

    #include <netdb.h>
    
    struct servent *getservbyport(int port, const char *protoname);
    

    其中 port 参数必须为网络字节序,例如:

    getservbyport(htons(53), "udp");
    

    相同的端口上,不同的协议可能有不同的服务!

    时间服务客户端改进

    可以通过上面所学对时间服务的客户端进行改进:

    int main(int argc, char** argv) {
        int sockfd, n;
        char recvline[MAXLINE + 1];
        struct sockaddr_in servaddr;
        struct in_addr** pptr;
        struct in_addr* inetaddrp[2];
        struct in_addr inetaddr;
        struct hostent* hp;
        struct servent* sp;
    
        if (argc != 3)
            err_quit("usage: daytimetcpcli1 <hostname> <service>");
    
        printf("%s:%s\n", argv[1], argv[2]);
        // 获取域名对应的地址
        if ((hp = gethostbyname(argv[1])) == NULL) {
            // 获取失败, 猜测可能是用户输入了IP地址,所以进行转换
            // 将 IP 地址从点分十进制转换为32位二进制
            if (inet_aton(argv[1], &inetaddr) == 0) {
                // 失败了
                err_quit("hostname error for %s: %s", argv[1], hstrerror(h_errno));
            } else {
                // 保存
                inetaddrp[0] = &inetaddr;
                inetaddrp[1] = NULL;
                pptr = inetaddrp;
            }
        } else {
            // 直接转换出来的就是32位二进制
            pptr = (struct in_addr**)hp->h_addr_list;
        }
    
        // 服务转换
        if ((sp = getservbyname(argv[2], "tcp")) == NULL)
            err_quit("getservbyname error for %s", argv[2]);
    
        // 循环,对查询得到的所有IP进行访问
        for (; *pptr != NULL; pptr++) {
            sockfd = Socket(AF_INET, SOCK_STREAM, 0);
    
            bzero(&servaddr, sizeof(servaddr));
            servaddr.sin_family = AF_INET;
            servaddr.sin_port = sp->s_port;
            memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
            printf("trying %s\n", Sock_ntop((SA*)&servaddr, sizeof(servaddr)));
    
            // 只要有一个成功
            if (connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) == 0)
                break; /* success */
            err_ret("connect error");
            close(sockfd);
        }
        if (*pptr == NULL)
            err_quit("unable to connect");
    
        // 输出
        while ((n = Read(sockfd, recvline, MAXLINE)) > 0) {
            recvline[n] = 0; /* null terminate */
            Fputs(recvline, stdout);
        }
        exit(0);
    }
    

    getaddrinfo 函数

    getaddrinfo 介绍

    优势:

    • 这个函数可以处理域名和地址的转换,以及服务和端口的转换
    • 返回一个 sockaddr 列表,而不是上面的结构,这些 sockaddr 可以直接使用
    • 把协议相关的内容隐藏了起来
    #include <netdb.h>
    
    int getaddrinfo(const char *hostname, // 主机名或地址串
                   const char *service, // 服务名或十进制端口号数串
                   const struct addrinfo *hints, // 填写对期望结果的暗示
                   struct addrinfo **result); // 返回的信息保存在这里
    

    参数解释

    • hints

      • hints 可以是一个空指针,也可以是一个指向某个 addrinfo 结构的指针
      • 调用者在其中可以填写关于期望返回信息类型的暗示,前四个属性都可以设置
    • result

      • 如果函数返回0,则会更新 result 对应的 addrinfo 链表
      • 链表可能有多个项,这取决于 hostname 关联了几个地址,以及是否有不同的协议类型
      • 链表是无序的,也就是说 TCP 未必会放在前面
      • 返回的 addrinfo 中的信息可以用于 socket 的相关操作,如 connect、sendto、bind
    • 如果 ai_flags 设置了 AI_CANONNAME ,那么返回的第一个 addrinfo 结构的 ai_canonname 指向所查找主机的规范名字

    addrinfo 结构

    // addrinfo
    struct addrinfo {
      int ai_flags; // 一些标志位,用来进行特殊的设置
      int ai_family; // AF_XXX 如 AF_INET、AF_INET6
      int ai_socktype; // SOCK_XXX 如 SOCK_STREAM SOCK_DGRAM
      int ai_protocol; // 协议名称,如 IPPROTO_TCP,如果前面两项可以唯一确认,则此项可为0
      socklen_t ai_addrlen;
      char *ai_canonname;
      struct sockaddr *ai_addr;
      struct addrinfo *ai_next;
    }
    

    下面对各个结构进行详细的解释:

    • ai_flags 用来设置一些标志位

      可用的标志值和含义如下:请忽略点符号,这里只是为了方便阅读

      标志值 作用
      AI_PASSIVE 套接字将用于被动打开(如服务器端)
      AI_CAN.ON.NAME 返回主机的规范名称,保存在返回的链表的第一项的 ai_canonname
      AI_NUMERIC.HOST 防止任何类型的名字到地址映射,hostname 必须是一个地址串
      AI_NUMERIC.SERV 放置任何类型的服务到端口映射,service 必须是十进制端口号数串
      AI_V4.MAPPED 如果同时指定 ai_family 为 AF_INET6,又没有 AAAA 记录,就返回 IPv4 对应的 IPv6 地址
      AI_ALL 如果同时指定上一项,则返回 IPv6 和 IPv4 对应的 IPv6 地址
      AI_ADDR.CONFIG 按照所在主机的配置选择返回地址类型
    • family、socktype、protocol 已经在注释上说的比较清楚了,他们可以直接被使用来操作 socket

    • ai_addrlen ai_addr 套接字结构的大小

    • ai_canonname 在上表格中提到了,在需要的时候是主机的规范名称

    • ai_addr 指向套接字地址的指针,已经被函数填充好了,且类型自适应

    • ai_next 指向下一位

    案例

    一个调用的案例:

    struct addrinfo hints, *res;
    
    bzero(&hints, sizeof(hints));
    hints.ai_flags  = AI_CANONNAME;
    hints.ai_family = AF_INET;
    
    getaddrinfo("freebsd4", "domain", &hints, &res);
    

    一个可能的结果:

    最佳实践

    在调用 getaddrinfo 时,共有 6 个可以选择的参数组合:

    • hostname、service
    • ai_flags、ai_family、ai_socktype、ai_protocol

    常见的组合方式如下所述

    客户端

    • TCP

      1. 指定 hostname 和 service
      2. 返回后,针对返回的所有 IP 地址,逐一调用 socket 和 connect,直到有一个连接成功,或全部失败
    • UDP

      1. 返回后,调用 sendto 或 connect

      2. 如果客户能够判定第一个地址看起来不工作,则尝试其余的地址

        两种判断方式:

        1. 已连接,获取到错误信息
        2. 未连接,等待消息超时
    • 如果客户端清楚套接字的类型,则应该设置 hints 为合适的值

    服务器

    • TCP

      1. 只指定 service,不指定hostname
      2. 在 hints 结构中指定 AI_PASSIVE 标志
      3. TCP 服务器随后调用 socket、bind、listen
    • UDP

      1. 调用 socket、bind、recvfrom
    • 如果服务器清楚套接字的类型,则应该设置 hints 为合适的值

    返回的 addrinfo 结构的数目

    返回的 addrinfo 的数目和暗示信息中 ai_socktype 的对应关系:

    gai_strerror

    #include <netdb.h>
    
    const char *gai_strerror(int error);
    

    作用:对于 getaddrinfo 的非 0 错误码,将该数值作为参数,输出其对应的错误信息

    freeaddrinfo

    作用

    getaddrinfo 返回的所有存储空间都是动态获取的(比如 malloc),包括:

    • addrinfo 结构
    • ai_addr 结构
    • ai_canonname 字符串

    这些存储空间通过 freeaddrinfo 返还给系统

    #include <netdb.h>
    
    void freeaddrinfo(struct addrinfo *ai);
    

    注意

    • 在头节点上调用,会释放整个 addrinfo 链表
    • 注意如果你采用浅拷贝使用了某些属性,则再访问时可能会因为该地址已经被释放而出错

    自己封装函数

    host_serv

    作用:简化 getaddrinfo 的步骤

    struct addrinfo* host_serv(const char* host,
                               const char* serv,
                               int family,
                               int socktype) {
        int n;
        struct addrinfo hints, *res;
    
        bzero(&hints, sizeof(struct addrinfo));
        hints.ai_flags = AI_CANONNAME; /* always return canonical name */
        hints.ai_family = family;      /* AF_UNSPEC, AF_INET, AF_INET6, etc. */
        hints.ai_socktype = socktype;  /* 0, SOCK_STREAM, SOCK_DGRAM, etc. */
    
        if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
            return (NULL);
    
        return (res); /* return pointer to first on linked list */
    }
    

    tcp_connect

    作用:创建一个 TCP 套接字并连接到一个服务器

    其步骤和上文最佳实践部分基本一致

    int tcp_connect(const char* host, const char* serv) {
        int sockfd, n;
        struct addrinfo hints, *res, *ressave;
    
        bzero(&hints, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
    
        if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
            err_quit("tcp_connect error for %s, %s: %s", host, serv,
                     gai_strerror(n));
        ressave = res;
    
        do {
            sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
            if (sockfd < 0)
                continue; /* ignore this one */
    
            if (connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
                break; /* success */
    
            Close(sockfd); /* ignore this one */
        } while ((res = res->ai_next) != NULL);
    
        if (res == NULL) /* errno set from final connect() */
            err_sys("tcp_connect error for %s, %s", host, serv);
    
        freeaddrinfo(ressave);
    
        return (sockfd);
    }
    

    时间程序客户端改进

    这个类似于前面部分的,只不过把部分步骤封装在 tcp_connect 中了!

    int main(int argc, char** argv) {
        int sockfd, n;
        char recvline[MAXLINE + 1];
        socklen_t len;
        struct sockaddr_storage ss;
    
        if (argc != 3)
            err_quit("usage: daytimetcpcli <hostname/IPaddress> <service/port#>");
    
        sockfd = Tcp_connect(argv[1], argv[2]);
    
        len = sizeof(ss);
        Getpeername(sockfd, (SA*)&ss, &len);
        printf("connected to %s\n", Sock_ntop_host((SA*)&ss, len));
    
        while ((n = Read(sockfd, recvline, MAXLINE)) > 0) {
            recvline[n] = 0; /* null terminate */
            Fputs(recvline, stdout);
        }
        exit(0);
    }
    

    tcp_listen

    int tcp_listen(const char* host, const char* serv, socklen_t* addrlenp) {
        int listenfd, n;
        const int on = 1;
        struct addrinfo hints, *res, *ressave;
    
        bzero(&hints, sizeof(struct addrinfo));
        hints.ai_flags = AI_PASSIVE;
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
    
        if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
            err_quit("tcp_listen error for %s, %s: %s", host, serv,
                     gai_strerror(n));
        ressave = res;
    
        do {
            listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
            if (listenfd < 0)
                continue; /* error, try next one */
    
            Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
            if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
                break; /* success */
    
            Close(listenfd); /* bind error, close and try next one */
        } while ((res = res->ai_next) != NULL);
    
        if (res == NULL) /* errno from final socket() or bind() */
            err_sys("tcp_listen error for %s, %s", host, serv);
    
        Listen(listenfd, LISTENQ);
    
        if (addrlenp)
            *addrlenp = res->ai_addrlen; /* return size of protocol address */
    
        freeaddrinfo(ressave);
    
        return (listenfd);
    }
    

    时间程序服务器端改进

    用 tcp_listen 代替部分步骤

    int main(int argc, char** argv) {
        int listenfd, connfd;
        socklen_t len;
        char buff[MAXLINE];
        time_t ticks;
        struct sockaddr_storage cliaddr;
    
        if (argc != 2)
            err_quit("usage: daytimetcpsrv1 <service or port#>");
    
        listenfd = Tcp_listen(NULL, argv[1], NULL);
    
        for (;;) {
            len = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA*)&cliaddr, &len);
            printf("connection from %s\n", Sock_ntop((SA*)&cliaddr, len));
    
            ticks = time(NULL);
            snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
            Write(connfd, buff, strlen(buff));
    
            Close(connfd);
        }
    }
    

    再次改进

    上述代码有一个问题:

    • tcp_listen 的第一个参数是 NULL
    • 而且 tcp_listen 内部指定的地址族为 AF_UNSPEC
    • 两者结合可能导致 getaddrinfo 返回非期望地址族的套接字地址结构

    用一个小技巧,可以指定,使用 IPv6 还是 IPv4:

    int main(int argc, char** argv) {
        int listenfd, connfd;
        socklen_t len, addrlen;
        char buff[MAXLINE];
        time_t ticks;
        struct sockaddr_storage cliaddr;
    
        if (argc == 2)
            listenfd = Tcp_listen(NULL, argv[1], &addrlen);
        else if (argc == 3)
            listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
        else
            err_quit("usage: daytimetcpsrv2 [ <host> ] <service or port>");
    
        for (;;) {
            len = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA*)&cliaddr, &len);
            printf("connection from %s\n", Sock_ntop((SA*)&cliaddr, len));
    
            ticks = time(NULL);
            snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
            Write(connfd, buff, strlen(buff));
    
            Close(connfd);
        }
    }
    

    测试案例:

    [root@centos-5610 names]# ./daytimetcpsrv2 0::0 daytime
    connection from [fe80::5054:ff:fe4d:77d3]:37428
    
    [root@centos-5610 names]# ./daytimetcpsrv2 0.0.0.0 daytime
    connection from 10.0.2.15:52178
    

    udp_client

    这个套接字地址结构的大小在 lenp 中返回,不允许是一个空指针(而TCP允许),因为 sendto 和 recvfrom 调用都需要直到套接字地址结构的长度

    int udp_client(const char* host,
                   const char* serv,
                   SA** saptr,
                   socklen_t* lenp) {
        int sockfd, n;
        struct addrinfo hints, *res, *ressave;
    
        bzero(&hints, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_DGRAM;
    
        if ((n = getaddrinfo(host, serv, &hints, &res)) != 0)
            err_quit("udp_client error for %s, %s: %s", host, serv,
                     gai_strerror(n));
        ressave = res;
    
        do {
            sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
            if (sockfd >= 0)
                break; /* success */
        } while ((res = res->ai_next) != NULL);
    
        if (res == NULL) /* errno set from final socket() */
            err_sys("udp_client error for %s, %s", host, serv);
    
        *saptr = Malloc(res->ai_addrlen);
        memcpy(*saptr, res->ai_addr, res->ai_addrlen);
        *lenp = res->ai_addrlen;
    
        freeaddrinfo(ressave);
    
        return (sockfd);
    }
    

    协议无关时间获取客户程序(UDP)

    这里协议无关指的是 IPv4 or IPv6

    int main(int argc, char** argv) {
        int sockfd, n;
        char recvline[MAXLINE + 1];
        socklen_t salen;
        struct sockaddr* sa;
    
        if (argc != 3)
            err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port#>");
    
        sockfd = Udp_client(argv[1], argv[2], (void**)&sa, &salen);
    
        printf("sending to %s\n", Sock_ntop_host(sa, salen));
    
        Sendto(sockfd, "", 1, 0, sa, salen); /* send 1-byte datagram */
    
        n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        recvline[n] = '\0'; /* null terminate */
        Fputs(recvline, stdout);
    
        exit(0);
    }
    

    udp_connect

    • 由于 connect 会保存相关的端口和端口信息,所以我们只需要知道返回的描述符就可以了
    • 和 tcp_connect 不同,UDP 的错误在发送一个数据报才能知晓(没有三次握手的过程)
    int main(int argc, char** argv) {
        int sockfd, n;
        char recvline[MAXLINE + 1];
        socklen_t salen;
        struct sockaddr* sa;
    
        if (argc != 3)
            err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port#>");
    
        sockfd = Udp_client(argv[1], argv[2], (void**)&sa, &salen);
    
        printf("sending to %s\n", Sock_ntop_host(sa, salen));
    
        Sendto(sockfd, "", 1, 0, sa, salen); /* send 1-byte datagram */
    
        n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        recvline[n] = '\0'; /* null terminate */
        Fputs(recvline, stdout);
    
        exit(0);
    }
    

    udp_server

    int main(int argc, char** argv) {
        int sockfd, n;
        char recvline[MAXLINE + 1];
        socklen_t salen;
        struct sockaddr* sa;
    
        if (argc != 3)
            err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port#>");
    
        sockfd = Udp_client(argv[1], argv[2], (void**)&sa, &salen);
    
        printf("sending to %s\n", Sock_ntop_host(sa, salen));
    
        Sendto(sockfd, "", 1, 0, sa, salen); /* send 1-byte datagram */
    
        n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        recvline[n] = '\0'; /* null terminate */
        Fputs(recvline, stdout);
    
        exit(0);
    }
    

    和上面 tcp_server 一样,可以通过那个小技巧来决定使用 IPv4 还是 IPv6

    getnameinfo

    • getaddrinfo 的互补函数
    • 以一个套接字地址为参数,返回描述其中的主机的一个字符串和描述其中的服务的另一个字符串
    • 协议无关,函数内部自行处理
    #include <netdb.h>
    
    int getnameinfo(const struct sockaddr *sockaddr, socklen_t addrlen,
                   char *host, sockelne_t hostlen, // 调用者预先分配
                   char *serv, socklent_t servlen, // 调用者预先分配
                   int flags);
    

    flags:

    常值 说明 备注
    NI_DGRAM 数据报服务 如果知道是UDP,则应设置,以免部分服务端口的冲突
    NI_NAME.REQD 若不能从地址解析出名字则返回错误
    NI_NO.FQDN 只返回FQDN的主机名部分 如a.foo.com,将截断为a
    NI_NUMERIC.HOST 以数串格式返回主机字符串 不要调用 DNS,
    NI_NUMERIC.SCOPE 以数串格式返回范围标识字符串
    NI_NUMERIC.SERV 以数串格式返回服务字符串 服务器通常应该设置这个标识

    可重入函数

    定义

    可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入 OS 调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如 全局变量区, 中断向量表 等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

    在使用时需要注意:

    • gethostbynamegethostbyaddrgetservbynamegetservbyport 都是不可重入的,因为它们都返回指向同一个静态结构的指针

    • inet_ptoninet_ntop 总是可重入的

    • 因为历史原因,inet_ntoa 是不可重入的,不过部分实现提供了使用线程特定数据的可重入版本

    • getaddrinfo 可重入的前提是由它调用的函数都可重入,这就是说,它应该调用可重入版本的 gethostbyname 和 getservbyname

    • getnameinfo 可重入的前提是由它调用的函数都可重入,这就是说,它应该调用可重入版本的 gethostbyaddr 和 getservbyport

    • errno 在每一个进程各有一个副本,但是多线程下也会发生被其他线程修改的情况

    解决方案

    • 不使用函数的不可重入版本

    • 就 errno 例子而言,可以使用类似如下的代码进行避免:

      void sig_alrm(int signo) {
        int errno_save;
        errno_save = errno;
        if (write( ... ) != nbytes) {
          fprintf(stderr, "Errno = %d\n", errno);
        }
        errno = errno_save;
      }
      

    可重入版本

    gethostbyname_r

    gethostbyaddr_r

    具体描述暂略

    本文没有提到的

    • 作废的IPv6地址解析函数
    • 其他网络相关信息

    由于此两章暂时用不到,故略


    1. Full Qualified Domain Name ↩︎


    __EOF__

  • 本文作者: LYMTICS
  • 本文链接: https://www.cnblogs.com/lymtics/p/16320004.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    mysql集群的主从复制搭建
    MYSQL索引详解和优化
    【Java八股文总结】之反射
    OpenCV4(C++)—— 视频和摄像头的加载、显示与保存
    基于Java+SpringBoot+Vue前后端分离失物招领平台设计和实现
    pytorch学习——循环神经网络RNN讲解及其实现
    比Selenium更优秀的playwright介绍与未来展望
    gin 统一响应结果
    appium+华为鸿蒙手机自动化(环境配置)
    web基础与http协议与配置
  • 原文地址:https://www.cnblogs.com/lymtics/p/16320004.html