read/write是socket数据交互的统一入口,遵循“一切皆文件”哲学;阻塞模式下read等待数据、write等待缓冲区空闲,非阻塞模式下二者立即返回,eagain表示无数据可读或缓冲区满;必须循环处理部分读写,不可假设原子完成。

read/write 是 socket 数据交互的统一入口
socket 本质上是“文件描述符”,所以 read 和 write 能直接操作它,无需额外接口。这种设计延续了 Unix “一切皆文件” 的哲学——不管背后是 TCP 连接、UDP 套接字还是本地管道,用户层看到的都是同一个系统调用签名:ssize_t read(int fd, void *buf, size_t count) 和 ssize_t write(int fd, const void *buf, size_t count)。内核根据 fd 对应的 socket 类型和协议栈状态,决定数据从哪来、往哪去、是否阻塞、是否拆包或合并。
阻塞与非阻塞模式下行为差异显著
默认创建的 socket 是阻塞的。此时:
- read:若接收缓冲区为空,进程挂起等待;一旦有数据到达(哪怕只有 1 字节),就立即拷贝到用户 buf,返回实际字节数(可能远小于 count)
- write:只要发送缓冲区有足够空间,就尽可能写入;若缓冲区满,则阻塞直到腾出空间,再继续写完剩余数据才返回
设为非阻塞后,两者都立刻返回:
- read 返回 0 表示对端关闭连接;返回 -1 且
errno == EAGAIN或EWOULDBLOCK表示当前无数据可读 - write 返回 -1 且
errno == EAGAIN表示发送缓冲区已满,需稍后重试
内核内部不只做简单拷贝,而是联动多层缓冲
看似一次 read/write,实际涉及至少三层缓冲协同:
- socket 接收/发送缓冲区:TCP 协议栈维护的环形缓冲,大小可调(如内核 5.4 默认 RCVBUF=128KB,SNDBUF=256KB)
- 页缓存(Page Cache):对普通文件有效,但对 socket 不启用;socket 数据绕过页缓存,直通协议栈
- 网卡 DMA 缓冲区:最终由驱动将数据从 socket 发送缓冲区经 DMA 搬运至网卡,减少 CPU 参与
这意味着:即使应用层 write 成功返回,数据未必已发出;同样,read 返回也不代表对方已收到 ACK。可靠性由 TCP 自身保障,而非系统调用语义。
必须处理“部分读写”,不能假设原子完成
无论阻塞与否,read/write 都不保证一次性完成 count 字节:
- read 可能因网络分片、MTU 限制、信号中断(EINTR)、对端短连接而只读几字节
- write 在阻塞模式下虽倾向写满,但仍可能因缓冲区瞬时不足或中断提前返回;非阻塞模式下更常见只写一部分
- 正确做法是用循环检查返回值,累计处理字节数,直到满足预期或遇到终止条件(如 EOF、错误、EAGAIN)
忽略这点是网络程序崩溃、丢数据、卡死的最常见根源之一。











