【问题标题】:How to recive more than 65000 bytes in C++ socket using recv()如何使用 recv() 在 C++ 套接字中接收超过 65000 个字节
【发布时间】:2012-03-29 06:31:13
【问题描述】:

我正在使用 C++ 在 Linux 中开发客户端服务器应用程序 (TCP)。我想同时发送超过65,000 字节。在 TCP 中,最大数据包大小仅为 65,535 字节。

我怎样才能无损地发送整个字节?

以下是我在服务器端的代码。

//Receive the message from client socket
if((iByteCount = recv(GetSocketId(), buffer, MAXRECV, MSG_WAITALL)) > 0) 
{
     printf("\n Received bytes %d\n", iByteCount);

     SetReceivedMessage(buffer);
     return LS_RESULT_OK;
}

如果我使用MSG_WAITALL 接收字节需要很长时间,那么如何设置标志以一次接收超过 100 万字节。

编辑:MTU 大小为 1500 字节,但 TCP 数据包大小的绝对限制为 65,535。

【问题讨论】:

  • TCP 旨在隐藏 IP 数据包大小作为传输的实现细节。路由器可以拆分数据包并以其他方式处理它们。您确定要依靠一个巨大的 100KB 数据包通过网络而不被切断吗?
  • TCP 是流协议,不要试图把它当作固定大小的数据包。接收尽可能多的字节,处理它们 - 你越早学会接受这是如何处理 TCP,你就会越快乐:-)
  • 十万字节”是什么意思?
  • 我需要向服务器发送超过 1000000 个字节,当我发送 65,635 或 100000 个字节时,它工作正常,如果我发送的超过这个,我的数据就会丢失......那么如何我可以处理吗

标签: c++ linux sockets tcp serversocket


【解决方案1】:

从上面的cmets来看,你似乎不明白recv是如何工作的,或者它应该如何使用。

真的想在循环中调用recv,直到您知道已收到预期的数据量或直到您获得“零字节读取”结果,这意味着另一端已关闭连接。永远,没有例外。

如果您需要同时执行其他操作(可能需要使用服务器进程!),那么您可能需要先使用pollepoll 检查描述符是否准备就绪。这让您可以在套接字准备就绪时多路复用。

您之所以要这样做,而且从来没有什么不同,是因为您不知道数据将如何打包以及数据包将如何(或何时)到达。另外,recv 不保证一次读取的数据量。它会在您调用它时提供其缓冲区中的内容,不多也不少(如果没有,它可能会阻塞,但是您仍然无法保证任何特定数量恢复时将返回的数据,它仍然可能返回例如 50 个字节!)。

即使您总共只发送 5,000 个字节,TCP 将其分解为 5 个(或 10 个或 20 个)数据包,而 recv 返回 500(或 100 个或 20 个)是完全有效的行为, 或 1) 字节,每次调用它。 这就是它的工作原理。
TCP 保证您发送的任何内容最终都会到达另一端或产生错误。而且,它保证您发送的任何内容都按顺序到达。它不能保证其他很多。最重要的是,它不能保证任何特定数量的数据在任何给定时间都准备好。
你必须为此做好准备,唯一的办法就是反复拨打recv。否则在某些情况下您将始终丢失数据。

MSG_WAITALL 原则上应该让它按照您期望的方式工作,但这是糟糕 的行为,并且不能保证它会正常工作。如果套接字(或网络堆栈中的某些其他结构)运行违反软或硬限制,它可能不会,并且可能不会满足您的请求。一些限制也很模糊。例如,由于实现细节的原因,SO_RCVBUF 的数字必须是您在 Linux 下预期接收的数字的两倍

服务器应用程序的正确行为应该从不依赖于诸如“它适合接收缓冲区”之类的假设。原则上,您的应用程序需要准备好使用 1 KB 接收缓冲区接收数 TB 的数据,如果需要,一次接收 1 个字节的块。更大的接收缓冲区会提高效率,但仅此而已......它仍然必须以任何方式工作。

您只能看到超过某个“巨大”限制的失败,这只是运气(或者更确切地说,是运气不好)。它显然“工作正常”到该限制这一事实表明您所做的事情是正确的,但事实并非如此。这是一个不幸的巧合。

编辑:
正如下面评论中所要求的,这可能看起来像这样(代码显然未经测试,警告购买者。

std::vector<char> result;
int size;

char recv_buf[250];

for(;;)
{
    if((size = recv(fd, recv_buf, sizeof(recv_buf), 0)) > 0)
    {
        for(unsigned int i = 0; i < size; ++i)
            result.push_back(recv_buf[i]);
    }
    else if(size == 0)
    {
        if(result.size() < expected_size)
        {
            printf("premature close, expected %u, only got %u\n", expected_size, result.size());
        }
        else
        {
            do_something_with(result);
        }
        break;
    }
    else
    {
        perror("recv");
        exit(1);
    }
}

这将接收您想要的任意数量的数据(或者直到分配了数百 MiB 大小的向量后,operator new 抛出 bad_alloc,但这是另一回事......)。

如果要处理多个连接,则需要添加pollepollkqueue 或类似功能(或... fork),我将把这个作为练习留给读者.

【讨论】:

  • 能否贴出recv函数的代码,这些函数在c++中使用recv()接收超过50kb的数据,
【解决方案2】:

您的问题可能与内核套接字缓冲区大小有关。尝试将以下内容添加到您的代码中:

int buffsize = 1024*1024;
setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffsize, sizeof(buffsize));

您可能还需要增加一些 sysctl 变量:

sysctl -w net.core.rmem_max=8388608
sysctl -w net.core.wmem_max=8388608

但是请注意,依靠 TCP 来填充整个缓冲区通常是一个坏主意。您应该多次调用 recv() 。您想要接收超过 64K 的唯一充分理由是为了提高性能。但是,Linux 应该已经具有自动调整功能,可以根据需要逐步增加缓冲区大小。

【讨论】:

  • 是的,我已经试过了,那行不通,实际上我需要发送一个 800kb 的文件,通过套接字通信到服务器,当 1 发送超过 80kb 时,它无法正确发送它只发送 65635字节或低于该范围的某个时间,我无法在我的服务器端获得确切的数据。无论故障是在我的客户端还是服务器端......
  • 他的问题是他不了解API或协议。提高套接字缓冲区通常是一个好主意,但它实际上并不能解决此线程中表达的任何特定困难。
【解决方案3】:

在 tcp max packet Sixe 中为 65,635,字节

不,不是。 TCP 是一种基于 IP 数据包分段的字节流协议,该协议在任何一个连接上都具有无限的传输大小。看看所有这些 100MB 的下载量:您认为它们是如何工作的?

只需发送和接收数据。你会明白的。

【讨论】:

  • 为了清楚起见:IP 数据包的最大大小(没有“巨型有效负载”选项)是 65535 字节,但 TCP 将您的数据分成尽可能多的 IP 数据包来发送所有数据。跨度>
  • 是的,我知道,但每个数据包的最大大小为 65635 字节,如果存在大小,则应该通过另一个数据包发送,现在我的问题是如何使用该 recv() 接收数据,如果我声明缓冲区大小为 char buffer[1000000],就像并将标志设置为 MSG_WAITALL 然后只有我得到整个字节,即使我丢失了一些字节,我已经在我的消息末尾设置了分隔符,如果有任何其他方式来接收数据没有损失
  • 这是我的 recv 函数 recv(GetSocketId(), buffer,MAXRECV,MSG_WAITALL)) > 0) 我使用缓冲区大小为 1000000,所以我花了很长时间来接收所有数据,所以我需要另一种方法,你能建议吗
  • MSG_WAITALL 用于当您知道消息的确切大小时——在连接终止或缓冲区已满之前调用不会返回。你想在一个循环中使用recv(),处理传入的数据。
  • 能否贴出recv函数的代码,这些函数在c++中使用recv()接收超过50kb的数据,
【解决方案4】:

我建议探索 kqueue 或类似的东西。使用事件通知,无需循环 recv 。只需在EV_READ 事件上调用一个简单的读取函数,并在触发事件的套接字上使用对recv 函数的一次调用。你的函数可以有一个 10 字节的缓冲区大小,或者你想要多少都没有关系,因为如果你第一次没有阅读整个消息,你只会在套接字上得到另一个 EV_READ 事件,你会回忆起你的 read 函数.读取数据时,您将收到 EOF 事件。无需忙于可能会或可能不会阻止其他传入连接的循环。

【讨论】:

    猜你喜欢
    • 2015-02-12
    • 2016-02-26
    • 1970-01-01
    • 2012-03-29
    • 2012-04-23
    • 2012-10-10
    • 2018-12-16
    • 1970-01-01
    • 2021-11-22
    相关资源
    最近更新 更多