``` static int fd_recv(int fd, char *buff, int size) { int rs = 0, len = 0; start: len = read(fd, buff + rs, size - rs); if (len < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { LOG_ERR("read block !!!!!!"); usleep(10); // 等待10ms goto start; } LOG_ERR("Failed to recv msg,fd=%d,buff=%p,size=%d, ret=%d,err=%s", fd, buff, size - rs, len, strerror(errno)); return QL_ERR_RPC_RECV_FAIL; } else if (len == 0) { LOG_ERR("Failure to Read,len = 0.client close"); return QL_ERR_RPC_SOCKET_CLOSE; } else if (len > 0 && len < size - rs) { LOG_ERR("Succeed to Read [Test]"); rs += len; goto start; } return QL_ERR_OK; }```套接字一次读取40000字节数据 报错Resource temporarily unavailable
时间: 2025-03-28 18:18:49 浏览: 28
<think>嗯,用户问的是关于一个C语言的函数`fd_recv`在读取40000字节数据时出现“Resource temporarily unavailable”错误的问题。首先,我需要仔细看看这个函数的代码,然后分析可能的原因。
首先,这个函数的功能是从文件描述符`fd`读取数据到缓冲区`buff`,直到读满`size`指定的字节数。用户提到的问题是当尝试一次读取40000字节时,报错。错误信息是“Resource temporarily unavailable”,这通常对应errno的EAGAIN或EWOULDBLOCK,表示非阻塞模式下没有数据可读。
接下来,我需要检查代码中的错误处理部分。在函数中,当`read`返回负数时,会检查errno是否为EINTR、EAGAIN或EWOULDBLOCK。如果是的话,会打印日志并调用`usleep(10)`后重试。不过用户遇到了这个错误,说明可能在某些情况下,即使重试了,依然无法读取足够的数据,导致函数返回错误。
可能的问题点有几个:
1. **非阻塞套接字**:如果套接字被设置为非阻塞模式,当没有数据可读时,`read`会立即返回EAGAIN/EWOULDBLOCK。代码中使用goto重试,并等待10微秒,但可能重试次数不够,或者发送端没有及时发送足够数据,导致持续遇到这个错误,最终触发某个超时或者错误返回。
2. **缓冲区大小不足**:用户试图读取40000字节,但提供的缓冲区`buff`是否足够大?如果缓冲区大小不足,可能会导致越界,但根据代码中的处理,`size`参数应该正确,所以可能不是这个问题。
3. **日志与实际情况不符**:当`len < 0`且errno是上述错误时,代码会打印“read block !!!!!!”,但用户看到的错误是“Resource temporarily unavailable”,这说明可能在某个情况下,代码没有正确处理这个错误,或者有其他地方的错误。
4. **阻塞模式下的问题**:如果套接字是阻塞模式,理论上`read`应该一直等待直到有数据可读。但代码中处理了EAGAIN,这可能在阻塞模式下不会出现,所以可能套接字实际上被设置为非阻塞模式,导致在数据未到达时返回错误。
另外,用户提供的函数在`len > 0 && len < size - rs`的情况下,会继续循环读取,直到读满`size`字节。这部分逻辑是正确的,但需要确认是否正确处理了所有可能的错误情况。
可能的原因:函数在非阻塞套接字上尝试读取大量数据(40000字节),而发送端没有一次性发送足够的数据,导致多次进入EAGAIN的处理,但由于某些情况下,比如发送端发送速度慢,或者网络延迟,导致重试次数不足,或者函数没有足够的时间等待数据到达,从而触发错误。
解决方案可能包括:调整套接字为阻塞模式,或者增加重试次数/延长等待时间,或者使用select/poll/epoll等机制来等待数据可读,而不是忙等待。另外,检查是否正确处理了所有可能的错误情况,比如是否在重试过程中有其他错误发生。
此外,代码中的日志在EAGAIN情况下打印的是“read block !!!!!!”,但用户看到的错误可能是来自另一个地方的日志,比如当函数返回错误码QL_ERR_RPC_RECV_FAIL时,上层可能记录了错误信息,其中包含了strerror(errno),即“Resource temporarily unavailable”。因此,可能函数在多次重试后,仍然无法读取数据,导致最终返回错误。
可能的改进方法:在非阻塞模式下,使用更高效的方式等待数据,比如结合epoll,或者增加重试次数,或者延长等待时间(比如将usleep(10)改为更大的值)。另外,检查套接字的阻塞模式设置,确保符合预期。</think>该代码是用于从套接字循环读取数据的函数。当尝试一次性读取40000字节时出现`Resource temporarily unavailable`错误,这属于典型的非阻塞套接字读写问题。以下是逐步分析及解决方案:
---
### 一、错误原因分析
1. **非阻塞模式特性**
- 当套接字设置为非阻塞模式(通过`fcntl(fd, F_SETFL, O_NONBLOCK)`)时,`read()`会立即返回`EAGAIN`或`EWOULDBLOCK`错误(对应`errno=11`)
- 错误含义:**内核缓冲区暂无数据可读**(对端未发送完数据或网络延迟)
2. **代码逻辑问题**
- 当前代码通过`usleep(10)`短暂等待后`goto`重试,但存在两个关键缺陷:
- **忙等待效率低**:10微秒(实际是10毫秒?代码中`usleep(10)`单位是微秒)的等待时间可能不足
- **无限重试风险**:若对端长时间未发送数据,会导致CPU空转
---
### 二、改进方案
#### 方案1:改用阻塞模式套接字(推荐)
```c
// 移除套接字的非阻塞标志
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
```
- **优点**:`read()`会阻塞直到数据到达或连接关闭
- **注意**:需确保代码中无其他位置设置非阻塞模式
#### 方案2:优化非阻塞模式的重试机制
```c
// 修改重试部分的等待策略
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
struct pollfd pfd = {fd, POLLIN, 0};
int ret = poll(&pfd, 1, timeout_ms); // 设置合理超时时间
if (ret > 0 && (pfd.revents & POLLIN)) {
goto start;
} else {
// 处理超时或错误
}
}
```
- **优点**:通过`poll()`监听可读事件,避免忙等待
---
### 三、其他注意事项
1. **TCP粘包问题**
- TCP是流式协议,无法保证单次`read()`调用返回完整数据包
- 需自定义协议头(如长度字段)确保读取完整数据
2. **缓冲区溢出风险**
- 确保`buff`的实际大小 ≥ `size`(代码中未做校验)
3. **日志误导性**
- 当`len > 0 && len < size - rs`时打印`LOG_ERR("Succeed to Read [Test]")`不合理,应改为`LOG_INFO`
---
### 四、完整改进代码示例
```c
static int fd_recv(int fd, char *buff, int size) {
int rs = 0, len = 0;
while (rs < size) {
len = read(fd, buff + rs, size - rs);
if (len < 0) {
if (errno == EINTR) {
continue; // 信号中断直接重试
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
struct pollfd pfd = {fd, POLLIN, 0};
if (poll(&pfd, 1, 5000) <= 0) { // 5秒超时
LOG_ERR("recv timeout");
return QL_ERR_RPC_RECV_FAIL;
}
} else {
LOG_ERR("Fatal read error: %s", strerror(errno));
return QL_ERR_RPC_RECV_FAIL;
}
} else if (len == 0) {
LOG_ERR("Connection closed by peer");
return QL_ERR_RPC_SOCKET_CLOSE;
} else {
rs += len;
}
}
return QL_ERR_OK;
}
```
---
### 五、总结
- **核心问题**:非阻塞模式下未正确处理`EAGAIN`
- **推荐方案**:优先使用阻塞模式,或在非阻塞模式下结合`poll()`/`epoll`
- **扩展建议**:增加协议解析逻辑处理TCP粘包
阅读全文
相关推荐


















