【问题标题】:recv receiving not whole data sometimerecv 有时接收到的不是全部数据
【发布时间】:2011-05-29 10:00:15
【问题描述】:

我有以下问题:这是代码块:

void get_all_buf(int sock, std::string & inStr) {
    int n = 1;
    char c;
    char temp[1024*1024]; 

    bzero(temp, sizeof(temp));

    n = recv(sock, temp, sizeof(temp), 0);

    inStr = temp;
};

但有时recv 不会返回整个数据(数据长度总是小于sizeof(temp)),只是它的一部分。写入方总是向我发送整个数据(我是通过嗅探器得到的)。有什么关系?谢谢。

P.S.我知道,礼貌建议我检查n (if (n < 0) perror ("error while receiving data")),但现在没关系 - 这不是我的问题的原因。

P.S.2我忘了 - 它正在阻塞套接字。

【问题讨论】:

  • 好的方式也建议检查您的输入。如果您收到的内容中没有 \0,那么充其量您的程序可能会崩溃,更糟糕的是,您可以获得一个精心制作的无效字符串,该字符串会利用该程序并 pwn 系统以获得乐趣和利润。
  • std::string?那么这是一个 C++ 问题,而不是 C 问题。

标签: c++ linux network-programming recv


【解决方案1】:

TCP 作为其他层之上的一层:IP 和以太网。 IP 允许数据碎片化,而以太网允许一些数据通过网络丢失。这会导致数据丢失,并反映在您对 recv 的调用中。

当您调用 recv 时,底层操作系统将尝试读取尽可能多的数据,直到您指定的大小,但可能会返回读取的字节数更少的调用,甚至一个字节。

您需要创建自己的一些协议来继续读取数据直到完成您的数据片段。

例如,您可以使用“\n”作为分隔符。这段代码可以改进,但我希望能让你明白:

void get_all_buf(int sock, std::string & inStr) {
    int n = 1, total = 0, found = 0;
    char c;
    char temp[1024*1024]; 

    // Keep reading up to a '\n'

    while (!found) {
        n = recv(sock, &temp[total], sizeof(temp) - total - 1, 0);
        if (n == -1) {
            /* Error, check 'errno' for more details */
            break;
        }
        total += n;
        temp[total] = '\0';
        found = (strchr(temp, '\n') != 0);
    }

    inStr = temp;
}

【讨论】:

    【解决方案2】:

    更好的方法是使用以下方法:

    void get_all_buf(int sock, std::string & output) {
        char buffer[1024];
    
        int n;
        while((errno = 0, (n = recv(sock, buffer, sizeof(buffer), 0))>0) || 
              errno == EINTR)
        {
            if(n>0)
                output.append(buffer, n);
        } 
    
        if(n < 0){
            /* handle error - for example throw an exception*/
        }
    };
    

    还要注意,分配在堆栈上的缓冲区要小得多。堆栈上有 1M 缓冲区可能会导致堆栈溢出。

    附加说明:您可能不想在套接字关闭之前阅读,因此您可能需要在 while 循环中添加另一个终止条件。

    【讨论】:

    • 如果你收到一个 EINTR 却没有收到任何数据,那么你应该忽略它并继续。
    • EINTR 是一个 errno 值。这意味着您应该将 errno 设置为 0,进行 recv 调用,然后将 errno 与 EINTR 进行比较,以查看您是否被信号中断
    • recv 将在 EINTR 上返回 -1 而不是 0。
    • @AlastairG:是的,我刚刚从手册页中弄清楚了。很久没有使用 BSD 套接字了。
    【解决方案3】:

    TCP 标准允许对数据包进行分段。实际上,这不会发生在几百字节左右的小数据包中,但几乎可以肯定的是,一兆字节的数据会碎片化。

    其次,当您说嗅探器说所有数据都已发送时,是一个数据包还是多个数据包?

    良好的网络编程习惯要求您不要假设消息以单个块的形式到达。两个连续的消息可以作为一个数据包到达(理论上,但实际上几乎从不),即使它们以多个数据包的形式到达,也可以作为单次读取来读取。一条消息可能会被分割成多个数据包,并且它们可能不会同时到达,这可能就是您所看到的。

    你的程序应该缓冲所有的读取,并有一种机制来确定整个消息何时到达,或者通过分隔符(例如,用 CRLFCRLF 分隔的 HTTP 标头)或通过字节数(例如,长度为在标头中指定)或通过关闭连接来指示数据的结束(例如,当标头中未指定内容长度时的 HTTP 主体)。可能还有其他机制。

    【讨论】:

    • 我已成功收到大约 30 kb 的数据。它支离破碎。但有时我无法接收 7-8 KB 的数据。
    • 这将取决于发送方如何对数据进行分段、网络有多忙、数据包如何到达,以及可能还有其他几个变量。我已经更新了我的答案,提供了有关如何应对它的建议,但只是一个简短的概述。我建议您在互联网上搜索有关网络编程的文章并进行研究。这并不难,但有很多事情需要考虑。你如何编写程序很大程度上取决于你在做什么。大多数关于套接字编程的文章都给出了非常糟糕的应用程序的不良示例,并且代码在现实生活中几乎没有用处。
    • @milo 因为你知道你期望的结构的大小,所以继续调用 recv 直到你读取了那么多字节。 for ( int total (0); total &lt; sizeof ( temp ); ) { int n = recv ( sock, temp + total, sizeof ( temp ) - total, 0 ); if ( n &lt; 0 ) abort(); total += n; }
    • 我明白你在说什么,但知道如何正确地做这件事对我来说很重要。因为我在书中看到了使用recv 的示例,它看起来像这样:n = read(newsockfd,buffer,255); if (n &lt; 0) error("ERROR reading from socket"); 所以......它正确吗?
    • TCP 与此无关。问题在于 recv() 函数。由于信号等原因,recv 可以返回任意数量的字节。
    猜你喜欢
    • 2012-04-23
    • 2013-04-03
    • 1970-01-01
    • 2020-08-29
    • 2016-06-02
    • 1970-01-01
    • 2012-07-05
    • 2015-08-19
    • 1970-01-01
    相关资源
    最近更新 更多