网络编程

DNS(IP)与服务名(端口)解析

DNS(IP)与服务名(端口)解析

1. getaddrinfo()

功能

将主机名和服务名转换为套接字地址结构列表,完成协议无关的地址解析。支持IPv4/IPv6双栈处理,自动过滤不支持的协议。

函数声明

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

int getaddrinfo(const char *host, const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);

参数解析

  1. host:主机名或IP地址字符串

    • 若为域名(如 "ex­am­ple.com"):触发 DNS 查询。域名字符串长度不小于netdb.h中定义的NI_MAXHOST(需启用_BSD_SOURCE)。
    • 若为IPv4/​IPv6 地址(如 "192.168.1.1" 或 "::1"):直接解析为地址结构。IP地址字符串长度不小于<netinet/in.h>中定义的INET_ADDRSTRLEN或INET6_ADDRSTRLEN。
    • 若为NULL:若在 hint 中指定 AI_­PAS­SIVE,表明服务端程序需要绑定到本地所有可用网络接口如(0.0.0.0 或 ::);若未指定,仅将服务名(如 "http")转换为端口号(如 80),不涉及主机名解析。
  2. service:服务名或端口号字符串

    • 可接受服务名(如"http")或十进制端口字符串(如"80")。服务字符串不小于netdb.h中定义的NI_MAXSERV(需启用_BSD_SOURCE)。十进制端口字符串无长度要求。
    • NULL表示不指定端口
  3. hints:过滤结果的模板结构体

    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; // 链表指针
    };
    • ai_flags 组合值:

      AI_PASSIVE     
      /* 不设置,就代表你是客户端,期望获取指定域名的服务器的套接字地址进行套接字连接。
            若host为空而service非空,则表明客户端希望getaddrinfo返回给其本地回环ip以通过bind绑定;
            若host非空而service为空,则表明客户端希望getaddrinfo返回给其一个0号端口,而0号端口会导致bind绑定一个空 
            闲临时端口以供发送
         若设置,就代表你是服务器,期望获取能够被客户端访问到的套接字地址,
            若host为空而service非空,则表明服务器希望getaddrinfo返回给其通配ip以监听所有网卡的service请求;
            若host非空而service为空,则表明服务器希望getaddrinfo返回给其一个0号端口,而0号端口会导致bind绑定一个空 
            闲临时端口以供接收*/
      AI_CANONNAME   // 返回规范主机名
      AI_NUMERICHOST // 禁止域名解析成IP地址,以减少函数不必要的检查(即我们必须传入一个IP地址字符串而非域名或NULL)
      AI_NUMERICSERV // 禁止服务解析成端口,以减少函数不必要的检查(即我们必须传入一个端口号而非服务名)
      AI_V4MAPPED    // IPv6返回IPv4映射地址
      AI_ALL         // 同时返回IPv4和IPv6映射

注意:某些系统(如 Linux)可能不允许 AI_CANONNAMEAI_PASSIVE 同时使用,因为 AI_PASSIVE 通常用于服务器监听,而 AI_CANONNAME 主要用于客户端查询主机名。

    • ai_family 取值:

      AF_UNSPEC  // 允许任意地址族
      AF_INET    // IPv4
      AF_INET6   // IPv6
    • ai_socktype 取值:

      SOCK_STREAM  // 流式套接字
      SOCK_DGRAM   // 数据报套接字
      0            // 任意类型
    1. res:返回的地址链表头指针

    返回值

    • 0:成功
    • 非零错误码:失败,需用gai_strerror()转换

      EAI_BADFLAGS   // 无效的ai_flags
      EAI_NONAME     // 无法解析名称
      EAI_AGAIN      // 临时故障
      EAI_FAIL       // 不可恢复错误
      EAI_MEMORY     // 内存分配失败
      EAI_SYSTEM     // 系统错误(检查errno)

    2. freeaddrinfo()

    功能

    释放getaddrinfo()动态分配的地址链表,避免内存泄漏。

    函数声明

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

    参数解析

    • res:getaddrinfo返回的地址链表头指针

    返回值

    无返回值


    3. gai_strerror()

    功能

    将getaddrinfo()/getnameinfo()的错误码转换为可读字符串。

    函数声明

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

    参数解析

    • errcode:getaddrinfo()或getnameinfo()返回的错误码

    返回值

    • 对应错误描述字符串(静态存储区,无需释放)

    4. getnameinfo()

    功能

    将套接字地址转换为对应的主机名和服务名,实现逆向解析。

    函数声明

    #include <sys/socket.h>
    #include <netdb.h>
    
    int getnameinfo(const struct sockaddr *sa, socklen_t salen,
                    char *host, socklen_t hostlen,
                    char *serv, socklen_t servlen,
                    int flags);

    参数解析

    1. sa:输入套接字地址结构指针

      • 支持struct sockaddr_in/sockaddr_in6等类型
    2. salen:地址结构实际长度

      • sizeof(struct sockaddr_in) 等
    3. host:输出主机名缓冲区

      • 长度应至少为NI_MAXHOST(1025)
    4. hostlen:主机名缓冲区长度

      • 使用宏定义:NI_MAXHOST
    5. serv:输出服务名缓冲区

      • 长度应至少为NI_MAXSERV(32)
    6. servlen:服务名缓冲区长度

      • 使用宏定义:NI_MAXSERV
    7. flags:控制标志组合

      NI_NOFQDN        // 仅返回主机名部分
      NI_NUMERICHOST   // 返回数字地址
      NI_NAMEREQD      // 无法解析时报错
      NI_NUMERICSERV   // 返回端口号而非服务名
      NI_DGRAM         // 指定数据报服务(用于端口映射)

    返回值

    • 0:成功
    • 非零错误码:同getaddrinfo()

    宏定义总结

    // 名称大小
    #define NI_MAXHOST 1025 //域名
    #define NI_MAXSERV 32 //服务名
    
    // IP地址presentation字符串最大长度
    #define INET_ADDRSTRLEN 16//IPv4
    #define INET6_ADDRSTRLEN 46 //IPv6
    
    // 地址族
    #define AF_UNSPEC 0 
    #define AF_INET   2
    #define AF_INET6  10
    
    // 套接字类型
    #define SOCK_STREAM 1
    #define SOCK_DGRAM  2

    以上函数配合使用可实现健壮的跨平台网络编程,正确处理了内存管理和错误处理,是构建现代网络应用的基础设施。

    回复

    This is just a placeholder img.