socket 非阻塞模式下的 recv 行为
非阻塞模式下如果当前无数据可读,recv 函数将立即返回,返回值为 -1,错误码为 EWOULDBLOCK。将客户端代码修成一下:
| |
执行结果与我们预期的一模一样, recv 函数在无数据可读的情况下并不会阻塞情绪,所以程序会一直有“There is no data available now.”相关的输出。
recv 指定长度
// recieve buffer
int RecvBuffer(int nSockfd, char *pBuffer, size_t nLength, size_t &nReceived)
{
LOG_ENTER;
size_t nReceive = 0;
int nRetry = 5;
nReceived = 0;
while (true && nRetry > 0)
{
// receive buffer
nReceive = recv(nSockfd, pBuffer+nReceived, nLength-nReceived, 0);
if (nReceive == -1)
{
LOG_ERROR("recv failed!errno=" << errno << "sockfd=" << nSockfd << ",buffer=" << pBuffer);
switch (errno)
{
case EAGAIN:
case EINTR:
{
nRetry--;
usleep(10);
continue;
}
case EBADF:
{
return 0;
}
default:
{
return -1;
}
}
}
else if (nReceive == 0)
{
LOG_ERROR("recv failed!errno=" << errno << "sockfd=" << nSockfd << ",buffer=" << pBuffer);
return 0;
}
else
{
// increase received bytes
nReceived += nReceive;
// test if be finished or not
if (nReceived >= nLength)
{
return nReceived;
}
}
}
return -1;
}
非阻塞模式下 SEND 和 RECV 函数的返回值总结
我们来根据前面的讨论来总结一下 send 和 recv 函数的各种返回值意义:
返回值 n | 返回值含义 |
---|---|
大于 0 | 成功发送 n 个字节 |
0 | 对端关闭连接 |
小于 0( -1) | 出错或者被信号中断或者对端 TCP 窗口太小数据发不出去(send)或者当前网卡缓冲区已无数据可收(recv) |
我们来逐一介绍下这三种情况:
-
返回值大于 0
对于 send 和 recv 函数返回值大于 0,表示发送或接收多少字节,需要注意的是,在这种情形下,我们一定要判断下 send 函数的返回值是不是我们期望发送的缓冲区长度,而不是简单判断其返回值大于 0。举个例子:
1 2 3 4 5
int n = send(socket, buf, buf_length, 0); if (n > 0) { printf("send data successfully\n"); }
很多新手会写出上述代码,虽然返回值 n 大于 0,但是实际情形下,由于对端的 TCP 窗口可能因为缺少一部分字节就满了,所以返回值 n 的值可能在 (0, buf_length] 之间,当 0 < n < buf_length 时,虽然此时 send 函数是调用成功了,但是业务上并不算正确,因为有部分数据并没发出去。你可能在一次测试中测不出 n 不等于 buf_length 的情况,但是不代表实际中不存在。所以,建议要么认为返回值 n 等于 buf_length 才认为正确,要么在一个循环中调用 send 函数,如果数据一次性发不完,记录偏移量,下一次从偏移量处接着发,直到全部发送完为止。
1 2 3 4 5 6
//不推荐的方式一 int n = send(socket, buf, buf_length, 0); if (n == buf_length) { printf("send data successfully\n"); }
| |
-
返回值等于 0
通常情况下,如果 send 或者 recv 函数返回 0,我们就认为对端关闭了连接,我们这端也关闭连接即可,这是实际开发时最常见的处理逻辑。
-
返回值小于 0
对于 send 或者 recv 函数返回值小于 0 的情况(即返回 -1),根据前文的讨论,此时并不表示 send 或者 recv 函数一定调用出错。这里列一个表格说明:
返回值和错误码 send 函数 recv 函数 操作系统说明 1 返回 -1,错误码 EWOUDBLOCK 或 EAGAIN TCP 窗口太小,数据暂时发不出去 当前内核缓冲区中无可读数据 Linux 2 返回 -1,错误码 EINTR 被信号中断,需要重试 被信号中断,需要重试 Linux 3 返回 -1,错误码不是上述 1 和 2 出错 出错 Linux 4 返回 -1,错误码 WSAEWOUDBLOCK TCP 窗口太小,数据暂时发不出去 当前内核缓冲区中无可读数据 Windows 5 返回 -1,错误码不是上述 4 出错 出错 Windows
注意:这里是针对非阻塞模式下 socket 的 send 和 recv 返回值,如果是阻塞模式下 socket,如果返回值是 -1(Windows 上即 SOCKET_ERROR),则一定表示出错。
阻塞与非阻塞的 SOCKET 的各自适用场景
阻塞的 socket 函数在调用 send、recv、connect、accept 等函数时,如果特定的条件不满足,就会阻塞其调用线程直至超时,非阻塞的 socket 恰恰相反。这并不意味着非阻塞模式的 socket 模式比阻塞模式的 socket 模式好,二者各有优缺点。
非阻塞模式的 socket,一般用于需要支持高并发多 QPS 的场景下(如服务器程序),但是正如前文所述,这种模式让程序执行流和控制逻辑变复杂;相反,阻塞模式逻辑简单,程序结构简单明了,常用于一些特殊的场景。这里举两个应用的场景:
示例一
程序需要临时发送一个文件,文件分段发送,每段对端都会的给与一个应答,程序可以单独开一个任务线程,在这个任务线程函数里面,使用先 send 后 recv 再 send 再 recv 的模式,每次 send 和 recv 都是阻塞式的。
示例二
A 端与 B 端之间只有问答模式,即 A 发送给 B 一个请求,B 必定会应答给 A 一个响应,除此以外,B 不会给 A 推送任何数据,也可以采取阻塞模式,每次 send 完请求后,就可以直接使用阻塞式的 recv 去接受一定要有的应答包。