第 59 章 Sockets: Internet 域 (Sockets: Internet Domains)

      +

      核心结论

      • Internet 域 socket 是基于 TCP/IP 的进程间通信:流式 socket (SOCK_STREAM) 在 TCP 之上提供可靠、双向字节流;数据报 socket (SOCK_DGRAM) 在 UDP 之上提供不可靠、无连接、消息边界通信。

      • 网络字节序为大端:不同硬件采用不同字节序 (big-endian vs little-endian),跨网络传输必须用 htonl/htons/ntohl/ntohs 转换为统一的 网络字节序 (大端) 以保证跨平台兼容。

      • IPv4 / IPv6 地址结构struct sockaddr_in (16 字节) 存 IPv4 地址 + 端口;struct sockaddr_in6 存 IPv6 (128 位) + 端口;struct sockaddr_storage 是大小足够的通用容器,可透明容纳 IPv4/IPv6,是协议无关编程的基石。

      • Protocol-independent 转换函数getaddrinfo() 接受主机名 + 服务名,返回 addrinfo 链表,内含 sockaddr 及 ai_family/ai_socktype;getnameinfo() 反向;二者替代过时的 gethostbyname / getservbyname,并同时处理 IPv4/IPv6。

      • inet_pton / inet_ntop 通用转换p = presentation、n = network;支持 IPv4 点分十进制与 IPv6 十六进制字符串与二进制形式互转;与过时的 inet_aton / inet_ntoa (仅 IPv4) 形成对比。

      • DNS 与 /etc/services:DNS 提供分布式 hostname → IP 映射(递归查询 + 迭代查询 + 缓存);/etc/services 静态映射 service-name ↔ port-number(IANA 集中维护);二者共同为上层 API 提供「名字 → 数字」的间接层。

      本章主旨

      本章是第 56-58 章概念铺垫后的 Internet 域编程实战。读者需要掌握:(1) 字节序问题与 htonl/htons 的真实语义——网络字节序固定为大端;(2) IPv4/IPv6 地址结构的字段差异 (sockaddr_in 16 字节 vs sockaddr_in6 更大的结构);(3) 现代协议无关函数 getaddrinfo/getnameinfo 的 hints 选项语义;(4) DNS 分布式查找机制如何支持 getaddrinfo 的 hostname 解析;(5) inet_sockets.c 风格的封装库如何消除代码中 IPv4/IPv6 分支。掌握这些后才能写出在双栈环境下透明运行的客户端/服务器代码。

      一、核心概念

      本章围绕 6 个核心概念展开:网络字节序、跨架构数据表示、Internet 地址结构、IP 地址文本 ↔ 二进制转换、协议无关的 host/service 解析、DNS 与服务名映射。

      概念 定义 + 重要性 实现提示

      网络字节序 (Network Byte Order)

      跨网络传输整数时统一使用的大端序;不同机器的 host byte order 不同(小端如 x86 / 大端如 PowerPC 等),必须通过 htonl/htons/ntohl/ntohs 显式转换,否则对端解错。

      §59.2;网络字节序 = 大端;函数名含义:host-to-network-long 等;常用于 sin_port/sin_addr 字段;同一字节序的机器上函数是 no-op。

      跨架构数据表示 (Data Representation)

      不同 C 数据类型在不同实现中长度不同(long 32/64 bit),结构体对齐差异导致填充字节不同;Marshalling 编码(XDR/ASN.1/CORBA/XML)解决异构互通;常见简化方案是 newline 分隔的纯文本协议(方便 telnet 调试)。

      §59.3;TLPI 第 59 章示例程序把整数编码为 \n-结束字符串;Listing 59-1 readLine();marshalling 标准:XDR (RFC 1014)、ASN.1-BER。

      Internet 域地址结构

      struct sockaddr_in(IPv4,含 sin_family/sin_port/sin_addr);struct sockaddr_in6(IPv6,含 sin6_family/sin6_port/sin6_addr/sin6_flowinfo/sin6_scope_id);struct sockaddr_storage 是大小足够通用的容器,避免 IPv4/IPv6 分支。

      §59.4;定义于 <netinet/in.h>;IPv4 地址常量:INADDR_ANYINADDR_LOOPBACK;IPv6 wildcard:in6addr_any;sockaddr_storage 大小通常 128 字节。

      inet_pton/inet_ntop 转换

      inet_pton (presentation → network) 将点分十进制/十六进制字符串转为二进制;inet_ntop 反向;支持 IPv4 和 IPv6;取代只能处理 IPv4 的 inet_aton/inet_ntoa(过时)。

      §59.6;定义 <arpa/inet.h>;缓冲区大小常量:INET_ADDRSTRLEN=16INET6_ADDRSTRLEN=46;返回值:1=成功、0=格式错、-1=err。

      getaddrinfo / getnameinfo (协议无关)

      getaddrinfo(host, service, hints, &result) 返回 addrinfo 链表(IPv4/IPv6 + stream/dgram);hints.ai_flags 控制 AI_PASSIVE/AI_CANONNAME/AI_NUMERICHOST/AI_V4MAPPED/AI_ADDRCONFIGgetnameinfo 反向;附带 freeaddrinfo 释放、gai_strerror 错误转字符串。

      §59.10;addrinfo 结构关键字段:ai_family/ai_socktype/ai_protocol/ai_addr/ai_addrlen/ai_canonname/ai_next;新代码必须用这两个函数,不用 gethostbyname

      DNS 与 /etc/services

      DNS 是 hostname ↔ IP 的分布式层级数据库(根域名 → TLD → 二级域 → 子域);客户端发递归请求至本地 DNS,后者做迭代查询;/etc/services 是本地静态文件,提供 service-name ↔ port 映射(IANA 集中注册 + 镜像分发)。

      §59.8–59.9;DNS 查询:getaddrinfo 调用 resolver → 本地 DNS → 根 → TLD → …​;/etc/services 格式:http 80/tcp;named(8) 是 BIND 实现;/etc/resolv.conf 控制域名补全规则。

      二、详细笔记

      59.1 Internet 域 socket 概览

      What:Internet 域 socket 的两种语义——基于 TCP 的 SOCK_STREAM 提供可靠、双向字节流;基于 UDP 的 SOCK_DGRAM 提供无连接、可能丢失 / 乱序 / 重复的消息服务。

      Why:理解 Internet 与 UNIX 域的差异——Internet 域 socket 地址由 (IP, port) 组成;UNIX 域由 (pathname) 组成;前者穿越主机边界,后者不。

      How

      维度 Internet 域 UNIX 域

      寻址

      (IP, port)

      pathname

      通信域

      跨主机

      同主机

      UDP 数据报可靠性

      不可靠(可能丢失/乱序/重复)

      UNIX 数据报可靠(基于本地 IPC)

      UDP 满队列处理

      静默丢弃

      send 阻塞

      流式 socket 协议

      TCP(连接建立 + 可靠字节流)

      本地字节流

      When:(1) 跨主机通信必须用 Internet 域;(2) 同主机且追求性能/可传 fd/凭证时可考虑 UNIX 域(第 57 章)。

      Example:典型 Internet 域 UDP echo 的 server recvfrom 收到 (claddr, len) 后回送;客户端 sendto 到 (svaddr)。

      59.2 网络字节序

      What:所有跨网络传输的多字节整数(sin_portsin_addr)必须使用统一的大端字节序(网络字节序);x86 等小端主机需用 htonl/htons 转换。

      Why:不同硬件在不同时代各自演化出不同的字节序——没有统一标准,跨平台协议就破环;TCP/IP RFC 793 选择大端作为标准,现代协议栈一律遵循。

      How

      函数 头文件 语义

      htons(uint16_t)

      <arpa/inet.h>

      host 16-bit → network 16-bit

      htonl(uint32_t)

      <arpa/inet.h>

      host 32-bit → network 32-bit

      ntohs(uint16_t)

      <arpa/inet.h>

      network 16-bit → host 16-bit

      ntohl(uint32_t)

      <arpa/inet.h>

      network 32-bit → host 32-bit

      函数定义通常为宏,在主机已经是大端时返回原值;名字源于早期 16/32-bit 区分。uint16_t/uint32_t 严格对应 16/32 位无符号整数。

      When:(1) 任何时候在 socket API 中把整数写入 sockaddr_in.sin_port / sin_addr;(2) 任何时候从这些字段读出整数在主机上做比较 / 数值运算。

      Example

      // 摘自《The Linux Programming Interface》 第 59 章
      #include <arpa/inet.h>
      
      struct sockaddr_in svaddr;
      memset(&svaddr, 0, sizeof(svaddr));
      svaddr.sin_family = AF_INET;
      svaddr.sin_addr.s_addr = htonl(INADDR_ANY);   /* host → network */
      svaddr.sin_port   = htons(PORT_NUM);          /* host → network */
      
      /* 读出时反向 */
      uint16_t port = ntohs(claddr.sin_port);

      59.3 数据表示与 marshalling

      What:异构机器之间的二进制结构体不能直接 write/read,因为 C 实现可能在字节序、int/long 宽度、结构 padding 上各不相同;解决办法是「marshalling」——按某种标准格式编码所有数据项。

      Why:一旦通讯双方机器架构 / 编译器 / 库版本不同,二进制直接交换会解错。即使本机暂同构,迁移到云端 / 跨平台时代码会失效。

      How:两种策略:

      1. 完整 marshalling:XDR (RFC 1014)、ASN.1-BER、CORBA、XML;为每种数据类型定义固定字节表示 + 类型 tag。

      2. 简化方案(多数应用采用):把数据编码成 \n-结束的文本(类似 HTTP 行分隔),可以用 telnet 手工调试。

      TLPI 提供 readLine()(Listing 59-1)从流式 socket 一次读一行的工具:

      // 摘自《The Linux Programming Interface》 第 59 章 Listing 59-1
      #include <unistd.h>
      #include <errno.h>
      ssize_t
      readLine(int fd, void *buffer, size_t n)
      {
          ssize_t numRead; size_t totRead; char *buf; char ch;
          if (n <= 0 || buffer == NULL) { errno = EINVAL; return -1; }
          buf = buffer; totRead = 0;
          for (;;) {
              numRead = read(fd, &ch, 1);
              if (numRead == -1) {
                  if (errno == EINTR) continue;
                  else return -1;
              } else if (numRead == 0) {
                  if (totRead == 0) return 0;
                  else break;
              } else {
                  if (totRead < n - 1) { totRead++; *buf++ = ch; }
                  if (ch == '\n') break;
              }
          }
          *buf = '\0';
          return totRead;
      }

      When:(1) 同种协议(同为 TCP)但运行在不同 arch 时——必须 marshall;(2) 大型 RPC / 中间件——用完整 XDR/ASN.1;(3) 简单文本协议——用 \n 分隔 + readLine

      Example:TLPI 的 is_seqnum_sv / is_seqnum_cl 把整数发给客户端用 snprintf("%d\n", seqNum),接收用 readLine

      59.4 Internet socket 地址结构

      What:IPv4 地址在 struct sockaddr_in(16 字节):sin_family=AF_INETsin_portsin_addr.s_addr(32 位 in_addr_t),加上填充对齐到 sockaddr 大小。IPv6 在 struct sockaddr_in6sin6_family=AF_INET6sin6_portsin6_addr (16 字节) + sin6_flowinfo + sin6_scope_id

      Why:协议栈需要统一的「地址结构体」字段视图;有了 sockaddr_storage,调用 bind/connect/accept 时无需知道对端是 v4 还是 v6——向上层 API 提供协议无关性。

      How

      类型 关键字段 用法

      in_addr (IPv4)

      s_addr (uint32)

      inet_pton / htonl(INADDR_ANY/INADDR_LOOPBACK) 初始化

      in6_addr (IPv6)

      s6_addr[16]

      wildcard 用 in6addr_any;loopback 用 in6addr_loopback

      sockaddr_in

      sin_family=AF_INET, sin_port, sin_addr

      IPv4 bind/connect 参数

      sockaddr_in6

      sin6_family=AF_INET6, sin6_port, sin6_addr + flowinfo/scope_id

      IPv6 bind/connect 参数

      sockaddr_storage

      足够大 (~128B)

      透明存 v4/v6;用 getnameinfo/accept 时转 (sockaddr*)

      IPv4/v6 共享同一端口号空间——绑 IPv6 端口会占住 IPv4 同样端口(视具体内核行为)。

      When:(1) 任何 bind/connect/sendto/accept 地址参数;(2) IPv4/IPv6 双栈代码首选 sockaddr_storage

      Example

      // 摘自《The Linux Programming Interface》 第 59 章
      struct sockaddr_in6 svaddr;
      memset(&svaddr, 0, sizeof(struct sockaddr_in6));
      svaddr.sin6_family = AF_INET6;
      svaddr.sin6_addr = in6addr_any;                  /* wildcard */
      svaddr.sin6_port = htons(SOME_PORT_NUM);
      bind(sfd, (struct sockaddr *) &svaddr, sizeof(struct sockaddr_in6));

      59.5–59.7 IP 地址转换与 datagram 客户端/服务器示例

      Whatinet_pton (presentation→network) 和 inet_ntop (network→presentation) 处理 v4/v6 的字符串 ↔ 二进制互转;TLPI 给出 IPv6 datagram echo 的完整示例(Listings 59-3 / 59-4)。

      Why:人脑爱字符串、协议栈要整数;inet_ntopgetnameinfo 廉价(不查 DNS)且保证成功。

      How

      函数 返回 失败场景

      inet_pton(AF_INET6, "::1", &addr)

      1 成功 / 0 格式错 / -1 err

      字符串无效

      inet_ntop(AF_INET, &addr, buf, len)

      buf / NULL

      len 太小 → ENOSPC

      缓冲区长度用 INET_ADDRSTRLEN=16 (v4) / INET6_ADDRSTRLEN=46 (v6)。

      When:(1) 用户从 argv 传 IP 字符串 → inet_pton;(2) 日志/打印 IP → inet_ntop;(3) v6 数据报服务器日志客户端地址用 inet_ntop(AF_INET6, &claddr.sin6_addr, str, INET6_ADDRSTRLEN)

      Example:TLPI Listing 59-3 的 IPv6 datagram echo server 关键循环:

      // 摘自《The Linux Programming Interface》 第 59 章 Listing 59-3
      for (;;) {
          len = sizeof(struct sockaddr_in6);
          numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
                              (struct sockaddr *) &claddr, &len);
          inet_ntop(AF_INET6, &claddr.sin6_addr, claddrStr, INET6_ADDRSTRLEN);
          printf("Server received %ld bytes from (%s, %u)\n",
                 (long) numBytes, claddrStr, ntohs(claddr.sin6_port));
          /* 大写转换后 sendto 回 client */
      }

      59.8–59.9 DNS 与 /etc/services

      What:DNS 是 hostname ↔ IP 的分布式层级命名系统——根、TLD(com / org / country-code)、二级域、子域,每层由不同组织管理的 zone。/etc/services 是 service-name ↔ port 的本地静态表(IANA 集中注册)。

      Why:若没有 DNS,中央管数百万 hostname 不可行;分布式 + zone 授权 + 缓存解决了扩展性。/etc/services 解决「服务名 → 端口号」的小规模映射,没必要走分布式协议。

      How

      DNS 查询机制: . 客户端调用 getaddrinfo("www.kernel.org") → resolver 发 DNS 请求到本地 DNS server(/etc/resolv.conf 指定)。 . 本地 DNS 收到「递归」请求;若无缓存 → 走「迭代」查询:根 → TLD(org.)→ 二级(kernel.org.)→ 返回 A/AAAA 记录。 . 本地 DNS 缓存并返回客户端。

      /etc/services 格式

      ssh             22/tcp
      http            80/tcp
      domain          53/udp          # 通常同名同端口,但少数反例(rsh 514/tcp vs syslog 514/udp)

      注:IANA 政策约定一个服务如有 v4/v6 都分配相同端口号;存在极少反例。

      When:(1) 程序接受 hostname —— getaddrinfo 会查 DNS;(2) 程序接受 service-name —— getaddrinfo/etc/services;(3) 多接口 hostname 解析返回多地址,getaddrinfo 给链表。

      Example:手动调试 DNS — dig . NS 列根服务器;dig www.kernel.org A 显式查询 A 记录。

      59.10–59.12 协议无关的 getaddrinfo / getnameinfo 与 inet_sockets 库

      Whatgetaddrinfo(host, service, hints, &result) 返回 addrinfo 链表,可同时覆盖 v4/v6 + stream/dgram。getaddrinfo 替代过时的 gethostbyname + getservbyname,是 IPv4/IPv6 通用的关键。getnameinfo 反向,freeaddrinfo 释放,gai_strerror 错误转字符串。

      Why:不依赖 getaddrinfo 的代码就只能写两套分支处理 v4/v6;有了它 + sockaddr_storage,任何单一 for (rp = result; rp; rp = rp→ai_next) { …​ } 循环就足以处理多地址。

      How

      addrinfo 结构:

      字段 含义

      ai_family

      AF_INET / AF_INET6

      ai_socktype

      SOCK_STREAM / SOCK_DGRAM

      ai_protocol

      协议(一般传 0)

      ai_addr, ai_addrlen

      实际 socket 地址(sockaddr_in / sockaddr_in6)

      ai_canonname

      仅首个节点,可选填充主名(需 AI_CANONNAME)

      ai_next

      链表下一个

      hints.ai_flags 关键值:

      Flag 作用

      AI_PASSIVE

      host=NULL 时返回 wildcard;用于 server bind

      AI_CANONNAME

      首个节点 ai_canonname 指向主名

      AI_NUMERICHOST

      host 必须为数字串;阻止 DNS 查询

      AI_NUMERICSERV

      service 为数字端口号;不查 /etc/services

      AI_V4MAPPED

      AF_INET6 无 v6 时返回 v4-mapped v6

      AI_ADDRCONFIG

      只返回本机有配置的协议栈

      host=NULL, !AI_PASSIVE

      默认返回 loopback

      service=NULL

      端口填 0

      gai_strerror() 处理的错误码:EAI_AGAIN / EAI_BADFLAGS / EAI_FAIL / EAI_FAMILY / EAI_MEMORY / EAI_NONAME / EAI_OVERFLOW / EAI_SERVICE / EAI_SOCKTYPE / EAI_SYSTEM

      TLPI Listing 59-8 给出封装库 inet_sockets.c

      函数 用途 实现提示

      inetConnect(host, service, type)

      client connect;返回 fd

      遍历 result 调用 socket+connect

      inetListen(service, backlog, addrlen)

      TCP server bind+listen

      通过 inetPassiveSocket(…​, doListen=TRUE)

      inetBind(service, type, addrlen)

      UDP server/client bind

      inetPassiveSocket(…​, doListen=FALSE)

      inetAddressStr(addr, addrlen, str, len)

      sockaddr → "(host, port)" 字符串

      内部调 getnameinfo

      When:(1) 写新代码——必须用 getaddrinfo/getnameinfo;(2) 想要 IPv4/IPv6 双栈透明——hints.ai_family = AF_UNSPEC + 遍历链表;(3) 想要 faster startup——AI_NUMERICHOST 跳过 DNS;(4) 想要 IP-only 日志——NI_NUMERICHOST 跳过反向。

      Example:TLPI Listing 59-6 顺序服务器关键片段:

      // 摘自《The Linux Programming Interface》 第 59 章 Listing 59-6
      memset(&hints, 0, sizeof(struct addrinfo));
      hints.ai_family   = AF_UNSPEC;
      hints.ai_socktype = SOCK_STREAM;
      hints.ai_flags    = AI_PASSIVE | AI_NUMERICSERV;
      
      if (getaddrinfo(NULL, PORT_NUM, &hints, &result) != 0)
          errExit("getaddrinfo");
      
      for (rp = result; rp != NULL; rp = rp->ai_next) {
          lfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
          if (lfd == -1) continue;
          if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval,
                         sizeof(optval)) == -1)
              errExit("setsockopt");
          if (bind(lfd, rp->ai_addr, rp->ai_addrlen) == 0)
              break;                 /* success */
          close(lfd);
      }
      listen(lfd, BACKLOG);

      59.13 过时的 API 与 UNIX vs Internet 域选择

      Whatinet_aton/inet_ntoagethostbyname/gethostbyaddrgetservbyname/getservbyport 是被 SUSv4 移除的过时函数,用法上局限于 IPv4(inet_aton)或非线程安全(全部共享静态返回指针)。同主机应用程序可以在 UNIX 域与 Internet 域之间二选一。

      Why:过时函数不可重入、IPv4-only;理解它们只是因为会在历史代码中碰到。新代码必须迁移至 inet_pton/ntopgetaddrinfo/nameinfo

      How

      过时函数 替代 备注

      inet_aton(addr_str, &in)

      inet_pton(AF_INET, …​, &in)

      仅 IPv4

      inet_ntoa(in)

      inet_ntop(AF_INET, &in, buf, len)

      返回静态指针,多线程不安全

      gethostbyname(name)

      getaddrinfo(…​)

      不支持 v6、不可重入

      gethostbyaddr(…​)

      getnameinfo(…​)

      同上

      getservbyname(…​)

      getaddrinfo(…​)

      同上

      getservbyport(…​)

      getnameinfo(…​)

      同上

      UNIX vs Internet 同主机选择:

      • UNIX 域性能更好(无协议栈 / 中断 / 拷贝开销)。

      • UNIX 域可用文件系统权限鉴权(sticky bit、owner/group)。

      • UNIX 域支持 fd passing 与 sender credentials(第 61 章)。

      • Internet 域可同时在同机 + 跨主机工作,无需修改。

      When:(1) 同主机 IPC + 需要传 fd 或凭证 → UNIX 域;(2) 潜在迁移到多主机 → Internet 域;(3) 兜底——同机用 UNIX 域前先评估两套 API 维护成本。

      Example:TLPI 给出 UNIX 域服务器 / 客户端(us_xfr_sv/cl)和 Internet 域版本(is_seqnum_sv/cl);练习 59-3 写 UNIX 域版本的 inet_sockets 库。

      三、关键图表

      关键系统调用 / 库函数
      函数 头文件 用途

      htonl/htons/ntohl/ntohs

      <arpa/inet.h>

      字节序转换

      inet_pton/inet_ntop

      <arpa/inet.h>

      IP 地址字符串 ↔ 二进制

      getaddrinfo

      <sys/socket.h>, <netdb.h>

      主机名 + 服务名 → socket 地址

      getnameinfo

      同上

      socket 地址 → 主机名 + 服务名

      freeaddrinfo

      同上

      释放 addrinfo 链表

      gai_strerror

      <netdb.h>

      EAI_* 错误码 → 字符串

      readLine (TLPI)

      自定义

      fd 上一行一读(含 EINTR 重启)

      inetListen/Bind/Connect/AddressStr

      <inet_sockets.h>

      TLPI 协议无关封装库

      关键常量与结构体
      名称 值 / 字段 出现位置

      INADDR_ANY

      0.0.0.0(wildcard)

      sin_addr.s_addr = htonl(INADDR_ANY)

      INADDR_LOOPBACK

      127.0.0.1(loopback)

      同上

      in6addr_any

      ::(v6 wildcard)

      sin6_addr = in6addr_any

      in6addr_loopback

      ::1

      同上

      INET_ADDRSTRLEN

      16

      inet_ntop v4 buf 大小

      INET6_ADDRSTRLEN

      46

      inet_ntop v6 buf 大小

      NI_MAXHOST

      1025

      getnameinfo host buf 大小(需 _BSD_SOURCE 等)

      NI_MAXSERV

      32

      getnameinfo service buf 大小

      SOCK_STREAM / SOCK_DGRAM

      socket 类型

      hints.ai_socktype

      AF_INET / AF_INET6 / AF_UNSPEC

      地址族

      hints.ai_family

      四、思维导图

      mindmap
        root((第 59 章 Internet 域))
          字节序
            大端网络序
            htonl htons
            ntohl ntohs
            跨架构兼容
          数据表示
            C long 宽度差异
            Marshalling XDR
            文本分隔协议
            telnet 调试
          地址结构
            sockaddr_in IPv4
            sockaddr_in6 IPv6
            sockaddr_storage
            INADDR_ANY LOOPBACK
            in6addr_any
          IP 字符串转换
            inet_pton
            inet_ntop
            INET_ADDRSTRLEN
            取代 inet_aton ntoa
          协议无关解析
            getaddrinfo
            getnameinfo
            addrinfo hints
            AI_PASSIVE NUMERIC
            inet_sockets 库
          DNS / services
            分布式层级
            递归迭代查询
            缓存加速
            etc services 静态

      五、重点与易错点

      1. 「网络字节序 = 大端」是铁律——sin_port/sin_addr 永远要 htons/htonl;忘记转换会在小端机器上误读对端数据。

      2. 永远 memset 全零再填字段——sockaddr_in6 还有 sin6_flowinfosin6_scope_id 两个字段虽常设 0,但混用 v4/v6 结构时容易留下脏数据。

      3. IPv6 wildcard vs loopback 用变量不用宏——IN6ADDR_ANY_INIT 是 initializer(只能用于声明),赋值语句必须用 in6addr_any;loopback 同理。

      4. 新代码只用 getaddrinfo,禁用 gethostbyname——后者是非线程安全、IPv4-only 的过时函数,SUSv4 已删除。新代码中应集中精力学会 hints.ai_flags 的用法。

      5. getaddrinfo(NULL, …​) vs getaddrinfo(NULL, …​, AI_PASSIVE, …​)——前者默认给 loopback(适合 client),后者给 wildcard(适合 server bind)。

      6. AI_NUMERICHOST 阻止 DNS 查询——argv 直接传 IP 字符串的场景用它,能省去 DNS 解析时间。

      7. traverse ai_next 链表必须 free——freeaddrinfo(&result) 释放整条链;中间要复制地址需先 memcpy 再释放。

      8. EAI_ 错误用 gai_strerror 不能 perror/strerror*——它返回的是 int 错误码(如 EAI_AGAIN),不是 errnoEAI_SYSTEM 才是「看 errno」。

      9. /etc/services 不是保留机制——文件中的端口号随时可能被占用;要让别人知道你的端口,去 IANA 注册而不是写文件。

      10. 同主机可选 UNIX 域——性能更好、可传 fd 可鉴权;但代码库多一份维护代价;可仿照练习 59-3 写 UNIX 版 inet_sockets 库。

      11. 端口 0 让内核分配 ephemeral——bind 后通过 getsockname 取实际端口;常用于 client 端,让内核选可用 ephemeral。

      12. IPv4-mapped IPv6 地址 ::ffff:a.b.c.d——双栈环境 v4 客户端可走 v6 server(若开启);AI_V4MAPPED 控制 getaddrinfo 是否返回这种地址。

      13. telnet 调试协议应用——把数据传输做成 \n-分隔文本,应用启动后 telnet host port 可手动测试,远胜 ad-hoc 客户端。

      14. inet_sockets 库是 TLPI 的精华——实际生产中通常会沉淀这种封装;不要在每个程序里都重复 for (rp = result; …​) 模板代码。

      15. 跨章衔接:第 60 章 Sockets: Server Design 演示这套 API 在迭代 / 并发服务器中的真实用法;第 61 章深入 TCP 状态机、shutdown()SO_REUSEADDR 与 OOB 数据;第 56-58 章铺垫 generic socket 与 TCP/IP;第 62 章起转向终端 / 备选 I/O 模型。