如果发送的数据小于 bufsize,recv(bufsize) 是否保证接收所有数据?

Is recv(bufsize) guaranteed to receive all the data if sended data is smaller then bufsize?

例如:

客户端

...
socket.connect(server_address)
data = some_message_less_than_100_bytes
socket.sendall(data)
...

服务器端

...
socket.accept()
socket.recv(1024)
...

服务器端是否保证一次接收到数据recv()

如果不是,使用 header 指定消息长度的标准解决方案如何工作? header 本身可能已被拆分,我们必须检查 header 是否已被正确接收。 或者 header 是定长的?这样无论发送多少条数据,接收方都可以始终以相同的方式解释前几个字节?

实际上我正在尝试做这样的事情

客户

while():
    send()
    recv()

服务器

recv()
while():
    send() # Acknowledge to client
    recv()

这是 ravi 在 Linux socket: How to make send() wait for recv()

中建议的

但我发现了上述问题。

ravi 的回答是否假设客户端和服务器都将接收对方在单个 recv() 中发送的内容?

更新

我很想post这张图片但是我不能因为信誉太差...

后面link是HTTP帧格式

https://datatracker.ietf.org/doc/html/rfc7540#section-4

确实是用了定长方案,所以不管header分割成多少块,都是一样的。

所以我想,某种 'fixed' 长度是唯一的解决方案?即使 header 大小本身是可变的,它也可能有一些承诺的位来指示 header 的长度。我说得对吗?

Is the server side guaranteed to receive the data in one recv()?

没有。 TCP是字节流,不是消息协议。虽然在大多数情况下它可能会处理小消息和空发送缓冲区,但如果发送的数据大于基础数据的 MTU link,它就会开始失败。除了单个八位字节之外,TCP 不保证任何 atomar send-recv 对。所以即使是小数据也不要指望它。

Is the server side guaranteed to receive the data in one recv()?

对于 UDP,是的。 recv() 将 return 要么是 1 个完整的数据报,要么是一个错误。但是,如果缓冲区大小小于数据报,则数据将被截断并且您无法恢复它。

对于 TCP,没有。您唯一的保证是,如果没有错误发生,那么 recv() 将 return 至少 1 个字节,但 不再 比指定的缓冲区大小,它可以 return 之间的任意字节数。

If not, how does the standard solution using header for specifying message length even works? The header itself could have been split and we have to check if header has been correctly received. Or the header is fixed length?

根据 header 的特定格式,它可以采用任何一种方式。许多协议使用fixed-lengthheaders,许多协议使用variable-lengthheaders.

无论哪种方式,您都可能需要多次调用 send() 以确保发送所有相关字节,并多次调用 recv() 以确保接收到所有字节。在 TCP 中发送和读取之间没有 1:1 关系。

Is the ravi's answer assuming that both client and server will receive what the other sent in a single recv()?

Ravi 的回答没有对 send() 发送和 recv() 接收的字节数做出任何假设。他的回答以更 higher-level 的视角呈现。但是,强制执行所需的行为非常简单,例如:

int sendAll(int sckt, void *data, int len)
{
    char *pdata = (char*) data;
    while (len > 0) {
        int res = send(sckt, pdata, len, 0);
        if (res > 0) {
            pdata += res;
            len -= res;
        }
        else if (errno != EINTR) {
            if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) {
                return -1;
            }
            /*
            optional: use select() or (e)poll to
            wait for the socket to be writable ...
            */
        }
    }
    return 0;
}

int recvAll(int sckt, void *data, int len)
{
    char *pdata = (char*) data;
    while (len > 0) {
        int res = recv(sckt, pdata, len, 0);
        if (res > 0) {
            pdata += res;
            len -= res;
        }
        else if (res == 0) {
            return 0;
        }
        else if (errno != EINTR) {
            if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) {
                return -1;
            }
            /*
            optional: use select() or (e)poll to
            wait for the socket to be readable ...
            */
        }
    }
    return 1;
}

这样就可以使用sendAll()发送消息header后跟消息数据,recvAll()接收消息header后跟消息数据。