【问题标题】:How to send messages with larger length than the buffer in socket programming?如何在套接字编程中发送长度大于缓冲区的消息?
【发布时间】:2015-02-21 19:02:19
【问题描述】:

我正在使用 C++ 中的 Winsock 开发一个应用程序。我有一个 200 字节长度的 char 数组,用于通过套接字发送字符串。

我的问题是发送大于 char 数组的消息时,所以我决定将它们分成多个块发送,但我不知道该怎么做。

为了发送和接收数据,我使用常规的send()recv() 函数:

recv(client, buffer, 200, NULL);
send(client, buffer, 200, NULL);

更新

我有一个结构:

struct Pack
{
    unsigned int senderId;
    char str[200];
}

在发送之前,我将 struct 转换为 char 数组。

Pack pk;
strcpy_s(pk.str, 200, "Some text to send.\0");
pk.senderId = 1 // user id
char *buffer = (char*)pk;

如果字符串大小大于 200,则 strcpy_s() 会崩溃。

【问题讨论】:

  • 除了下面答案中描述的分块机制之外,接收端还需要知道消息的长度,以便它知道何时获得了将要发送的所有数据。这就是你需要设计协议的地方——接收方如何知道数据何时全部到达。一种常见的技术是使用 TLV(类型、长度、值)编码,其中消息包含类型指示符(如果一切都是字符串,则可选),然后是数据的长度,然后是那么多字节的数据。跨度>
  • 如果我是你,我会使用 ZeroMQ 来传输任意大小的数据包(称为“消息”),而不管底层传输机制如何。以这种方式设置和获得可靠的网络要容易得多。
  • 请注意,strcpy_s() 设计为在源字符串长于目标字符串时失败。当您传输数据时,您可能正在处理字节数组而不是字符串。您可能不会通过网络发送终止的空值,但接收方需要将数据组装成以空值终止的字符串。

标签: c++ c sockets winsock


【解决方案1】:

你在Beej's Guide to Network Programming中有一个例子:

7.3。处理部分 send()s

记得在上面关于 send() 的部分,当我说过 send() 可能不会发送您要求的所有字节?也就是说,你想要 它发送 512 个字节,但它返回 412。发生了什么 剩余 100 字节?

嗯,它们仍在您的小缓冲区中等待发送出去。到期的 在你无法控制的情况下,内核决定不发送 所有的数据都在一个块中,现在,我的朋友,由你决定 把数据拿出来。

你也可以写一个这样的函数来做到这一点:

int sendall(int s, char *buf, int *len)
{
    int total = 0;        // how many bytes we've sent
    int bytesleft = *len; // how many we have left to send
    int n;

    while(total < *len) {
        n = send(s, buf+total, bytesleft, 0);
        if (n == -1) { break; }
        total += n;
        bytesleft -= n;
    }

    *len = total; // return number actually sent here

    return n==-1?-1:0; // return -1 onm failure, 0 on success
} 

编辑:正如@a​​lk 所指出的,将所有ints 替换为ssize_t - send() 返回的类型

【讨论】:

  • 所有ints 但n 应该是size_tn 应该是 ssize_t
【解决方案2】:

如果您正在发送或接收 UDP 数据包,则无法解决;你需要一个足够大的缓冲区来容纳整个数据包。幸运的是,在大多数情况下,无论如何您都不想发送或接收大于大约 1500 字节的 UDP 数据包,(因为这是以太网卡可以发送的最大数据包,而不会将其分割成更小的数据包,并且通常您希望避免碎片化,如果可能)。

对于 TCP,请记住您正在发送/接收字节流,而不是一系列单独的数据包,因此您传递给 send() 和 recv() 的大小通常不会对应于大小无论如何,网络数据包。这意味着调用 send() 6 次,每次调用 100 字节数据,与调用 send() 1 次 600 字节数据并没有太大区别,依此类推。正如其他人所指出的,您必须仔细检查每个 send() 和 recv() 调用的返回值,以查看 实际 发送或接收了多少字节,因为发送/接收的数字可以 (并且会)有时会小于您请求的字节数。您需要知道实际发送了多少字节才能知道哪些字节适合在下次调用时发送,并且您需要知道实际接收了多少字节才能知道现在有多少字节在您的接收中有效-缓冲区。

【讨论】:

    【解决方案3】:

    两端都需要缓冲。

    当你send 某事时,你不能保证你的所有缓冲区都会被发送。 send(2) 系统调用可能会返回部分字节数

    同样,当您的recv 某些东西时,字节流可能是部分的。 recv(2) 系统调用可能会返回部分字节数。

    您可能需要一些事件循环(例如上面的poll(2))来混合发送和接收。

    TCP/IP 不保证给定的send(在发射机中)对应于一个recv(在接收机中):字节流可能已被分块成任意片段(例如,通过路由器) )。另见this

    【讨论】:

    • 那么,你是说如果我发送“AA BBBB CCC DDDDDD E”它可能会以“A ABB B BCC CDDD DDDE”的形式到达接收端,对吧?假设如果我在长度前加上消息并发送“10ABCDEFGHIJ”,那么长度字节本身可能会被分段?在这种情况下,我应该编写一个脚本来整理消息吗?
    • 是的,见我刚才在回答中提到的stackoverflow.com/a/20582916/841108
    【解决方案4】:

    逐条发送消息。您只能发送与发送缓冲区大小一样大的片段,因此您需要记住您发送了多少以及剩余多少;那么您可以使用循环来执行所需的操作:

    int bufferSize = 200;
    int messageLength = 442; // or whatever
    int sendPosition = 0;
    
    while (messageLength) {
        int chunkSize = messageLength > bufferSize ? bufferSize : messageLength;
        memcpy(buffer, message + sendPosition, chunkSize);
        chunkSize = send(client, buffer, chunkSize, NULL);
        messageLength -= chunkSize;
        sendPosition += chunkSize;
    }
    

    当然,您可以简单地从message 发送,而不是先将不必要的副本复制到buffer,但我只是在这里说明这个概念。

    然后,您的接收代码可能需要在能够处理它之前完整地组装消息,但这实际上取决于您设计的协议。

    【讨论】:

    • 如何在服务器上将其显示为一个完整的消息?在另一边收到它之前,我知道尺寸是多少。
    • @user3530012:这意味着您需要在内容之前传输消息大小,因此您需要您的消息有一个标题。接收代码需要能够在没有额外信息的情况下确定此标头的大小(否则有先有鸡的问题);最简单的方法是使标题固定大小。
    • @user3530012:您需要设计协议,以便知道数据应该在接收端保留多长时间。您可以提前发送长度,或者您可以对消息使用 JSON 等编码,当您获得正确的结束字符(通常是匹配的 } 开头 @ 987654325@).
    • 发布的示例代码没有检查 send() 返回的值——如果不这样做,它将无法可靠地工作。每当程序处于非平凡负载下时,典型的症状就是“丢失字节”(当 send() 返回的值小于 chunkSize 时,字节丢失,但代码循环没有注意到这一点,并假设 chunkSize 字节是已发送)。
    • @JeremyFriesner:是的,感谢您的建议。虽然我并不是真的打算在这里提供生产代码,但我认为这很重要,应该包含在 PoC 中。我会相应地进行编辑。
    猜你喜欢
    • 2010-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-08
    • 1970-01-01
    • 2015-09-02
    • 1970-01-01
    • 2012-04-16
    相关资源
    最近更新 更多