在 C/C++网络编程中,尤其是在处理 TCP 套接字时,recv
函数扮演着基石般的角色。它是从连接对端读取传入数据的主要机制。
recv
函数原型
我们先从标准的 POSIX 函数原型开始:
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
: 接收数据的套接字文件描述符。
buf
: 指向用于存储接收数据的缓冲区的指针。
len
: buf
缓冲区的最大长度,即本次调用最多接收的字节数。
flags
: 控制接收操作的标志位(例如 MSG_PEEK
, MSG_WAITALL
)。通常设为 0 表示标准行为。
返回值 ssize_t
类型对于理解调用的结果至关重要。
解读 recv
返回值
recv
函数的返回值主要有以下三种情况:
返回值 > 0: 成功接收数据
含义: 调用成功,并从对端接收到了数据。
值: 返回值表示实际接收到并放入buf
中的字节数。
注意: 这个值可能小于你请求的len
,即使发送方发送了更多的数据。这是正常现象,原因包括网络缓冲区大小、TCP 分段、以及调用时套接字接收缓冲区中实际可用的数据量等。处理: 处理接收到的
返回值
这么多字节的数据。如果你的应用层协议期望更多的数据,你可能需要在一个循环中再次调用recv
,直到接收到完整的消息或发生错误/连接关闭。
返回值 == 0: 对端已正常关闭连接
含义: 远端对等方(peer)已经执行了有序关闭(graceful shutdown)序列(发送了 FIN 包),表示它不会再发送任何数据了。
值:0
。
注意: 这不是一个错误。这是一个信号,表明连接的读取方向已经由对端关闭。你无法再从此连接接收到任何数据。处理: 识别到连接正在关闭。通常应停止尝试接收数据,可能需要关闭你自己的写端(如果还没关闭,使用
shutdown(sockfd, SHUT_WR)
),并最终调用close(sockfd)
来释放套接字资源。
返回值 == -1: 发生错误
含义:
recv
调用失败。
值:-1
。处理: 这是错误处理的关键所在,不同的值处理方式是不一样的。
错误处理:哪些 errno
值是“可接受”的?
当 recv
返回 -1 时,并非所有的 errno
值都意味着连接已死或必须立即放弃。有些错误指示的是临时状态或需要特定处理逻辑的情况,而不是直接终止连接。这些就是在面试题里面说的“可接受”的错误。
“可接受” / 非致命错误: EAGAIN
或 EWOULDBLOCK
: (这两个宏通常具有相同的值)
场景: 主要发生在套接字被设置为非阻塞模式 (O_NONBLOCK
) 时。
含义: 当前套接字的接收缓冲区中没有数据可读,并且在不阻塞的情况下无法立即完成读取操作。这不是一个真正的错误,而是对套接字状态的一种描述。
处理: 不要关闭连接。这是非阻塞 I/O 中的预期行为。标准的处理方式是使用 I/O 多路复用机制(如 epoll
, select
, poll
)。这些机制会通知你套接字何时变为可读状态,届时你再安全地重试 recv
调用。在一个紧密循环中反复调用 recv
而不等待(忙等待)是非常低效的。
// 概念性的非阻塞循环(结合epoll/select)
while (true) {// 使用epoll_wait, select等等待sockfd变为可读// ... 等待逻辑 ...ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0);if (bytes_received > 0) {// 处理数据...} else if (bytes_received == 0) {// 对端关闭连接handle_close(sockfd);break;} else { // bytes_received == -1if (errno == EAGAIN || errno == EWOULDBLOCK) {// 当前无数据可用,返回继续等待(epoll/select)continue;} else if (errno == EINTR) {// 被信号中断,直接重试continue;} else {// 发生其他错误perror("recv failed"); // 打印错误信息handle_error_close(sockfd); // 处理错误并关闭连接break;}}
}
EINTR
:
致命 / 不可恢复错误:
这些错误通常表明连接本身、套接字状态或程序逻辑存在问题。在这些错误发生后继续在该套接字上操作通常是无意义或不可能的。
ECONNRESET
: 连接被对端重置。对方发送了 RST(重置)包,很可能是因为对方异常终止或网络问题。连接已失效。
ENOTCONN
: 套接字未连接(例如,在 TCP 套接字上调用connect
或accept
之前,或连接已断开后尝试recv
)。
ETIMEDOUT
: 连接超时。可能在连接建立阶段发生,或在数据传输过程中由于网络状况极差或设置了SO_RCVTIMEO
并发生较长超时而发生。通常意味着连接不可用。
ECONNREFUSED
: 远程主机主动拒绝连接(更常见于connect
调用,但在特定的 UDPrecvfrom
场景下也可能遇到)。
EBADF
: 无效的文件描述符 (sockfd
没有指向一个打开的套接字)。这是程序逻辑错误。
EFAULT
: 传入的缓冲区指针buf
指向了进程地址空间之外的无效内存。这是程序逻辑错误。
EINVAL
: 提供了无效的参数(例如,无效的flags
)。程序逻辑错误。
ENOTSOCK
: 文件描述符sockfd
指的不是一个套接字。程序逻辑错误。
对于致命错误的处理: 记录具体的 errno
值和错误信息(使用 perror
或 strerror
),清理与该连接相关的资源,并调用 close(sockfd)
关闭套接字。
回答
1、区分出 recv
的三种返回值 (>0, 0, -1) 及其含义。
3、知道 -1
需要检查 errno
。
3、明确指出 EAGAIN
/EWOULDBLOCK
和 EINTR
是可接受的、需要特殊处理(等待/重试)的错误,尤其是在非阻塞 I/O 场景下 EAGAIN
/EWOULDBLOCK
是正常情况。
对于“什么错误是可接受的?”这个问题,最核心的答案是 EAGAIN
(或 EWOULDBLOCK
) 和 EINTR
。