在网络编程中,很多初学者会误以为:调用write就是把数据直接发到了网线上,调用read就是直接从网线上抓取数据。
事实并非如此。你的程序其实是在和操作系统的内核缓冲区打交道。
一、 核心机制:套接字中的文件描述符与缓冲区
当服务器通过accept建立连接,或客户端通过connect连接成功后,双方都获得了一个用于通信的文件描述符(File Descriptor)。
1. 两个 FD 的区别(服务器端)
- 监听 FD (
lfd):- 来源:
socket()->bind()->listen()。 - 作用:只负责在那“站岗”,通过
accept()接受新的连接请求。 - 读缓冲区:存储的是待处理的新连接请求(已完成三次握手)。
- 来源:
- 通信 FD (
cfd):- 来源:
accept()的返回值。 - 作用:专门负责和某一个特定的客户端进行数据传输。
- 读缓冲区:存储的是客户端发来的数据。
- 来源:
2. 双向缓冲机制
每一个通信 FD在内核中都维护着两块内存区域:
- 写缓冲区 (Write Buffer):
- 当你调用
write/send时,数据只是从应用程序被拷贝到了这个缓冲区。 - 内核协议栈会在合适的时机(自动)将数据打包通过网络发送出去。
- 当你调用
- 读缓冲区 (Read Buffer):
- 网卡收到的数据,由内核接收并存放在这里。
- 当你调用
read/recv时,你是从这里拿走数据。
TCP 的流式特性:正因为有缓冲区的存在,收发两端的数据量可以不对等。
- 例如:客户端每 5 秒发送 4KB 数据。
- 服务器端:可以每秒读取 100 字节,只要缓冲区没溢出,数据就不会丢。
二、 函数对比:Read/Write vs Recv/Send
在 Linux 网络编程中,标准文件 IO 函数和 Socket 专用 IO 函数经常混用。
1. 接收数据
// 标准文件IOssize_tread(intfd,void*buf,size_tcount);// Socket专用ssize_trecv(intsockfd,void*buf,size_tlen,intflags);- 区别:
recv多了一个flags参数。 - flags:通常设置为0。此时
recv等同于read。 - 常用 flag:
MSG_PEEK(查看数据但不从缓冲区取出)、MSG_DONTWAIT(非阻塞模式)。
2. 发送数据
// 标准文件IOssize_twrite(intfd,constvoid*buf,size_tcount);// Socket专用ssize_tsend(intsockfd,constvoid*buf,size_tlen,intflags);- 同理,当
flags为 0 时,send等同于write。
三、 代码实战:使用 Recv/Send 实现数据传输
我们将编写一个简单的 Echo 服务器和客户端,演示recv和send的使用,以及connect的连接流程。
1. 服务器端 (server_recv.c)
服务器负责接收客户端发来的数据,并原样发回。
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include