第 56 章 Sockets 概述 (Sockets: Introduction)

      +

      核心结论

      • Socket 三要素:通信域(AF_UNIX/AF_INET/AF_INET6)+ 类型(SOCK_STREAM/SOCK_DGRAM)+ 协议(几乎总是 0)。

      • 通信域:AF_UNIX(本机进程间,路径名寻址)、AF_INET(IPv4 网络)、AF_INET6(IPv6 网络);AF_ 前缀用 address family,PF_ 兼容同义。

      • Socket 类型对比:SOCK_STREAM 可靠、双向、字节流(类似 pipe,连接导向);SOCK_DGRAM 不可靠、消息边界、无连接(UDP)。

      • 核心 API:socket()、bind()、listen()(仅 stream)、accept()(仅 stream)、connect()、close()、read/write、send/recv、sendto/recvfrom。

      • stream socket 流程:服务器 socket→bind→listen→accept(阻塞等客户端);客户端 socket→connect→读写→close。

      • datagram socket 流程:服务器 socket→bind→recvfrom;客户端 socket→sendto→recvfrom(可选);不需 listen/accept。

      • connect 对 datagram 的意义:可设置「默认对端」——之后可用 write 代替 sendto;用 AF_UNSPEC 解绑(Linux 支持,SUN 之前未定义)。

      • socket I/O 兼容:可用 read/write(通用)或 send/recv/sendto/recvfrom(socket 特有);可 fcntl F_SETFL O_NONBLOCK 非阻塞;ioctl FIONREAD 查可用字节数(Linux)。

      • SOCK_CLOEXEC / SOCK_NONBLOCK:2.6.27+ socket() 第二参数可 OR 这两个标志——避免额外 fcntl;accept4() 同样支持。

      • socket 系统调用与 socketcall:Linux 上多数 socket 调用通过 socketcall 多路复用——但接口上仍是独立调用。

      本章主旨

      本章是第 57-61 章 socket 编程的总览——读者需要建立 socket 心智模型:(1) socket = 通信端点(fd);(2) 域决定寻址方式;(3) 类型决定通信语义;(4) API 模式:socket 创建 → bind/listen 命名 → connect/accept 协商 → read/write/recv/send 数据。stream vs datagram 是最重要的二分——stream 是电话模型(建立连接 + 双向字节流),datagram 是邮件模型(无连接 + 消息 + 不可靠)。后续章节分别讲 UNIX 域(本机)和 Internet 域(网络)。

      一、核心概念

      本章围绕 6 个核心概念展开:socket 三要素、通信域、socket 类型、stream 流程、datagram 流程、socket I/O。

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

      通信域(AF_UNIX/AF_INET/AF_INET6)

      AF_UNIX 本机(路径名,sockaddr_un);AF_INET IPv4(IP+port,sockaddr_in);AF_INET6 IPv6(sockaddr_in6);AF_ 与 PF_ 同义(SUSv3 用 AF_)

      §56.1 表 56-1;AF_LOCAL 是 POSIX.1g 中 AF_UNIX 同义词,SUNv3 不使用

      Socket 类型(SOCK_STREAM/SOCK_DGRAM)

      SOCK_STREAM 可靠 + 双向 + 字节流(连接导向,类似 TCP);SOCK_DGRAM 不可靠 + 消息边界 + 无连接(UDP)

      §56.1 表 56-2;stream 适合大量数据可靠传输;datagram 适合小消息低延迟

      通用地址结构 sockaddr

      struct sockaddr { sa_family_t sa_family; char sa_data[14]; }——所有 bind/connect/accept 用此类型指针;实际传入域特定结构(sockaddr_un/in/in6)

      §56.4;通过 (struct sockaddr*) cast;addrlen 标识结构大小;SUSv3 不含 sa_len 字段

      Stream socket 流程

      服务器:socket→bind→listen→accept(阻塞)→{读写}→close;客户端:socket→connect→{读写}→close;listen 标记为被动 socket;accept 创建新已连接 socket

      §56.5;图 56-1;backlog 参数限制 pending 连接数;SOMAXCONN Linux=128(/proc/sys/net/core/somaxconn 可调)

      Datagram socket 流程

      服务器:socket→bind→recvfrom→{处理}→close;客户端:socket→sendto→可选 recvfrom→close;不需 listen/accept;可 connect 设置默认对端

      §56.6;图 56-4;recvfrom 返回发送方地址;recvfrom 一次只读一个 datagram;过长截断

      connect on datagram

      datagram socket 可 connect——内核记录对端;之后可用 write 替代 sendto;只接收对端 datagram;可用 AF_UNSPEC 解绑(Linux 支持)

      §56.6.2;非对称——只影响本 socket;典型于「多 datagram 发同一对端」;Linux 对性能影响小(其他 UNIX 可能提升)

      二、详细笔记

      56.1 socket() 创建

      Whatsocket(domain, type, protocol) 返回 fd——通信端点。

      Why:所有 socket 通信的起点。

      How

      // 摘自《The Linux Programming Interface》 第 56 章
      #include <sys/socket.h>
      int socket(int domain, int type, int protocol);
      
      /* 创建 UNIX 域 stream socket */
      int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
      if (sfd == -1) errExit("socket");
      
      /* 2.6.27+ 可 OR SOCK_CLOEXEC / SOCK_NONBLOCK */
      int sfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);

      domain 取值:AF_UNIX、AF_INET、AF_INET6。

      type 取值:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW(高级);SOCK_SEQPACKET(SCTP)。

      protocol 几乎总是 0(让内核选);SOCK_RAW 用 IPPROTO_RAW。

      When:(1) 服务器/客户端通信——socket 是 fd-based IPC 端点;(2) 同主机——首选 AF_UNIX(比 AF_INET 127.0.0.1 快);(3) 跨主机——AF_INET/AF_INET6。

      Examplesocket(AF_UNIX, SOCK_STREAM, 0) 创建 UNIX 域 stream socket;socket(AF_INET, SOCK_DGRAM, 0) 创建 UDP socket。

      56.2 bind() 绑定地址

      Whatbind(sockfd, addr, addrlen) 把 socket 绑定到地址——服务器端常用。

      Why:让客户端找到服务器——「well-known address」。

      How

      // 摘自《The Linux Programming Interface》 第 56 章
      #include <sys/socket.h>
      int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
      
      /* 通用 sockaddr 模板 */
      struct sockaddr {
          sa_family_t sa_family;
          char        sa_data[14];
      };
      
      /* UNIX 域 */
      struct sockaddr_un addr = { .sun_family = AF_UNIX };
      strcpy(addr.sun_path, "/tmp/mysock");
      bind(sfd, (struct sockaddr*)&addr, sizeof(addr));

      addr 参数:(struct sockaddr*) 是模板,实际传 sockaddr_un/sockaddr_in/sockaddr_in6。

      When:(1) 服务器必须 bind(让客户端知道地址);(2) 客户端 UDP 通常 bind ephemeral port(OS 自动选);(3) UNIX 域客户端可不 bind(OS 自动)。

      Example:服务器 bind("/tmp/mysock");客户端 connect("/tmp/mysock")——通过文件系统路径寻址。

      56.3 stream socket 流程(电话模型)

      What:stream socket 是面向连接的通信——双方建立连接后双向字节流传输。

      Why:可靠 + 有序 + 双向——大量数据传输首选。

      How

      // 摘自《The Linux Programming Interface》 第 56 章
      /* 服务器 */
      int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
      bind(sfd, (struct sockaddr*)&addr, sizeof(addr));
      listen(sfd, BACKLOG);                      /* 标记被动 */
      int cfd = accept(sfd, NULL, NULL);         /* 阻塞等连接 */
      if (cfd == -1) errExit("accept");
      /* read/write cfd */
      close(cfd);
      close(sfd);
      
      /* 客户端 */
      int cfd = socket(AF_UNIX, SOCK_STREAM, 0);
      connect(cfd, (struct sockaddr*)&addr, sizeof(addr));
      /* read/write cfd */
      close(cfd);

      listen backlog:内核为已连接但未 accept 的连接维护队列;SOMAXCONN Linux = 128(可调 sysctl net.core.somaxconn)。

      When:(1) HTTP/SSH 等 TCP 服务——stream socket;(2) 高并发服务器——listen backlog 设大;(3) accept 多路复用——select/poll/epoll。

      Example:第 59 章典型 TCP echo 服务器——socket→bind→listen→循环 accept→fork/thread 处理。

      56.4 datagram socket 流程(邮件模型)

      What:datagram socket 是无连接——每次 sendto 指定目标地址;recvfrom 返回发送方地址。

      Why:低延迟 + 简单;适合小消息、查询/响应类应用(DNS)。

      How

      // 摘自《The Linux Programming Interface》 第 56 章
      #include <sys/socket.h>
      ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                     const struct sockaddr *dest, socklen_t addrlen);
      ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                       struct sockaddr *src, socklen_t *addrlen);
      
      /* 服务器 */
      int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
      bind(sfd, (struct sockaddr*)&addr, sizeof(addr));
      char buf[BUF_SIZE];
      struct sockaddr_un peer;
      socklen_t plen = sizeof(peer);
      ssize_t n = recvfrom(sfd, buf, sizeof(buf), 0,
                           (struct sockaddr*)&peer, &plen);
      sendto(sfd, buf, n, 0, (struct sockaddr*)&peer, plen);
      
      /* 客户端 */
      int cfd = socket(AF_UNIX, SOCK_DGRAM, 0);
      sendto(cfd, msg, len, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
      n = recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);

      recvfrom 一次只读一个 datagram;过长截断(用 recvmsg + MSG_TRUNC 探测)。

      When:(1) DNS/查询响应——UDP/datagram;(2) 实时游戏/语音——UDP 低延迟;(3) 小消息 + 不需可靠——datagram。

      Example:第 57 章 UNIX 域 datagram 示例——ud_ucase_cl.c 客户端 sendto,服务器 recvfrom + 转大写 + sendto 回应。

      56.5 connect() on datagram socket

      What:datagram socket 可 connect——内核记录默认对端;之后可用 write 代替 sendto。

      Why:(1) 简化 I/O(write vs sendto);(2) 过滤(只接收对端的 datagram);(3) 某些 UNIX 性能提升。

      How

      /* datagram 客户端:connect 后用 write/read */
      int cfd = socket(AF_UNIX, SOCK_DGRAM, 0);
      connect(cfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
      write(cfd, "hello", 5);                    /* 不需指定地址 */
      char buf[BUF_SIZE];
      read(cfd, buf, sizeof(buf));               /* 只接收 server_addr 的 datagram */
      
      /* 解绑 */
      struct sockaddr_un unspec = { .sun_family = AF_UNSPEC };
      connect(cfd, (struct sockaddr*)&unspec, sizeof(unspec));   /* Linux 支持 */

      When:(1) 客户端发多个 datagram 给同一服务器——connect 后用 write;(2) 多服务器场景——不可 connect(要换服务器)。

      Example:典型 UDP 客户端——socket→connect→循环 write/read→close。

      56.6 Socket I/O 通用接口

      What:socket fd 与普通 fd 一样可用 read/write;也有 send/recv/sendto/recvfrom 特有调用。

      Why:(1) read/write 通用;(2) send/recv 加 flags(MSG_DONTWAIT/MSG_PEEK 等);(3) sendto/recvfrom 处理地址。

      How

      调用 适用 特点

      read / write

      stream + datagram

      通用;datagram 一次一消息

      send / recv

      stream + datagram(connect 后)

      flags 参数(MSG_DONTWAIT 等)

      sendto / recvfrom

      datagram(无 connect)

      含目标/源地址

      recvmsg / sendmsg

      高级

      msghdr 多缓冲、cmsg 传递 fd

      非阻塞:fcntl(sfd, F_SETFL, O_NONBLOCK);2.6.27+ SOCK_NONBLOCK

      可用字节数:ioctl(sfd, FIONREAD, &cnt)(Linux)。

      When:(1) 简单 stream——read/write 够;(2) 高级特性——send/recv 加 flags;(3) datagram 收发——sendto/recvfrom 或 read/write(connect 后)。

      Example:典型 stream 服务器 read(cfd, buf, n) / write(cfd, buf, n);datagram 服务器 recvfrom(sfd, buf, n, 0, &peer, &plen)

      三、关键图表

      非可视化条目(API / 域 / 类型)
      类别 内容

      通信域

      AF_UNIX(本机路径名)/ AF_INET(IPv4 IP+port)/ AF_INET6(IPv6)

      Socket 类型

      SOCK_STREAM(TCP-like 可靠字节流)/ SOCK_DGRAM(UDP-like 不可靠消息)

      协议参数

      几乎总是 0;SOCK_RAW 用 IPPROTO_RAW

      通用地址结构

      struct sockaddr { sa_family_t sa_family; char sa_data[14]; };实际用 sockaddr_un/in/in6 cast

      域特定结构

      sockaddr_un(路径名)/ sockaddr_in(IPv4 32 位 + 16 位端口)/ sockaddr_in6(IPv6 128 位 + 16 位端口)

      Stream API

      socket → bind → listen → accept / socket → connect → read/write → close

      Datagram API

      socket → bind → recvfrom / socket → sendto → 可选 recvfrom

      listen backlog

      SOMAXCONN Linux=128;/proc/sys/net/core/somaxconn 可调

      accept 行为

      阻塞等连接;返回新 fd(已连接 socket);原 listening socket 仍开

      stream 关闭

      close 触发 EOF(对端 read 返回 0);写触发 SIGPIPE + EPIPE(应忽略 SIGPIPE)

      datagram 关闭

      不需 close 即可丢弃未读 datagram

      connect on datagram

      内核记录对端;write 替代 sendto;过滤只收对端;AF_UNSPEC 解绑(Linux)

      Socket I/O

      read/write(通用);send/recv/sendto/recvfrom(特定);recvmsg/sendmsg(高级)

      通用 flags

      SOCK_CLOEXEC(close-on-exec)/ SOCK_NONBLOCK(非阻塞);2.6.27+

      accept4

      accept + flags 参数;2.6.28+

      ioctl FIONREAD

      查可用字节数;Linux 特有

      四、思维导图

      mindmap
        root((第 56 章 Sockets 概述))
          三要素
            domain 通信域
            type 类型
            protocol 0
            socket fd
          通信域
            AF_UNIX 本机
            AF_INET IPv4
            AF_INET6 IPv6
            sockaddr 模板
          类型对比
            SOCK_STREAM 可靠
            SOCK_STREAM 字节流
            SOCK_STREAM 连接导向
            SOCK_DGRAM 不可靠
            SOCK_DGRAM 消息边界
            SOCK_DGRAM 无连接
          Stream 流程
            服务器 bind listen
            服务器 accept 阻塞
            客户端 connect
            read write 双向
            close EOF
            SIGPIPE EPIPE
          Datagram 流程
            服务器 bind
            服务器 recvfrom
            客户端 sendto
            一次一消息
            过长截断
          connect datagram
            设置默认对端
            write 替代 sendto
            过滤只收对端
            AF_UNSPEC 解绑
          Socket IO
            read write 通用
            send recv flags
            sendto recvfrom 地址
            recvmsg sendmsg 高级
            SOCK_CLOEXEC
            SOCK_NONBLOCK
      
      == 五、重点与易错点
      
      . *Socket = fd*——socket() 返回 fd;可用 read/write/close/poll/epoll 等通用 fd 操作。
      . *AF_ vs PF_*——AF_UNIX/AF_INET/AF_INET6 用 address family;PF_ 同义但 SUSv3 不标准化;用 AF_。
      . *通用 sockaddr 是模板*——bind/connect/accept 都用 `struct sockaddr*`;实际传入域特定结构 + cast。
      . *addrlen 参数*——bind/connect 时是结构大小;accept/recvfrom 是 value-result(先设大小,返回实际大小)。
      . *Stream vs Datagram 选择*——可靠大量数据 stream;小消息低延迟 datagram;不能混用模式。
      . *Stream 必须 listen+accept*——服务器端 socket 经 listen 后才能 accept;客户端才能 connect。
      . *Datagram 不需 listen/accept*——直接 sendto/recvfrom;可 connect 设置对端。
      . *accept 返回新 fd*——不是返回 sockfd;listening socket 仍开,可继续 accept。
      . *Stream 关闭触发 EOF*——对端 read 返回 0;写触发 SIGPIPE——必须忽略 SIGPIPE。
      . *Datagram 关闭不通知对端*——不需 close 即可,未读 datagram 丢失。
      . *listen backlog 含义*——已连接但未 accept 的队列;SOMAXCONN 是系统上限。
      . *connect on datagram 是非对称*——只影响本 socket;不通知对端。
      . *AF_UNSPEC 解绑 datagram*——Linux 支持;SUSv3 未明确;SUSv4 标准化;其他 UNIX 可能不支持。
      . *send/recv vs read/write*——send/recv 多 flags 参数(MSG_DONTWAIT/MSG_PEEK/MSG_WAITALL);read/write 通用但不能传 flags。
      . *SOCK_CLOEXEC / SOCK_NONBLOCK*——2.6.27+ socket() 直接 OR;避免额外 fcntl;accept4() 类似。
      . *跨章衔接*:第 57 章 AF_UNIX(本机 socket);第 58 章 TCP/IP 协议基础;第 59 章 AF_INET/AF_INET6 网络 socket;第 60 章 socket 服务器设计;第 61 章 socket 高级(shutdown/sendmsg/TCP_NODELAY 等)。