TCP可以任意合并和拆分数据包。所以,假设我做了这样一个电话:
`recv(sock, buf, 15, 0)`
但目前只有5
字节的数据可以立即使用 -
recv()
阻塞直到15
个字节的数据可用,或者我只获得5
个字节?
我问,因为我想知道我是否可以简单地以这种方式阅读uint32_t
(假设i
是uint32_t
类型的变量):
if(recv(sock, &i, sizeof(uint32_t), 0) < sizeof(uint32_t)) {
/* error */
}
i = ntohl(i);
或者我宁愿做类似的事情:
unsigned char buff[sizeof(uint32_t)];
ssize_t read_already = 0;
while(read_already != sizeof(uint32_t)) {
ssize_t read_now = recv(sock, buff, sizeof(uint32_t)-read_already, 0);
if(read_now == -1) {
/* error */
}
else {
read_already += read_now;
}
}
memcpy(&i, buff, sizeof(uint32_t));
i = ntohl(i);
后者显然更加丑陋且更加艰巨,但是如果recv()
在收到所有请求的数据之前没有阻止,则可能是必要的。
答案 0 :(得分:1)
通常,在没有设置任何特殊标志,套接字选项或ioctl的情况下,阻塞TCP套接字上的recv
调用将返回小于或等于请求大小的任意数量的字节。但除非套接字被远程关闭,信号中断或处于错误状态,否则它将阻塞,直到至少有1个字节可用。
换句话说,如果您要求15个字节,但只有5个字节可用,recv
将返回5.
应用程序开发人员不应该依赖于数据的发送或构造方式,并将其套接字视为能够随时返回部分数据流。 (或者,正如我告诉其他人一样,编写代码,就好像recv
一次只返回1个字节一样。)
接收数据的常见循环通常如下所示。注意在recv调用中buffer
上完成了指针数学运算。
unsigned char buffer[bytes_expected];
ssize_t bytes_received = 0;
while (bytes_received < bytes_expected)
{
int result = recv(sock, buffer + bytes_received, bytes_expected-bytes_received, 0);
if (result == 0)
{
// socket was closed remotely - break out of the loop
}
else if (result < 0)
{
// socket was closed on remote end or hit an error
// either way, the socket is likely dead
break;
}
else
{
bytes_received += result;
}
}
我知道此规则的例外情况是基于MSG_WAITALL
的{{1}}标志作为recv
的最后一个参数。这将保留套接字(块),直到您获得传递给length
的{{1}}参数所请求的所有字节(或直到错误或套接字关闭)。
MSG_WAITALL(自Linux 2.2起)此标志请求该操作 阻止直到满足完整请求。但是,电话可能仍然存在 如果信号被捕获,则返回的数据少于请求的数据,错误或 发生断开,或者接收的下一个数据是不同的 类型而不是返回
你甚至被问到这个问题的事实让你在大多数其他不熟悉套接字的人中排在联盟之上。现实世界中存在如此多的错误代码,因为大多数开发人员都没有处理recv返回的内容少于预期的情况。