【问题标题】:Winsock receiving extra dataWinsock 接收额外数据
【发布时间】:2012-07-30 16:05:12
【问题描述】:

我尝试使用 Winsock 制作文件下载器,但我意识到如果 Internet 连接速度较慢,客户端会收到一些不需要的数据以及服务器发送的数据。
于是我做了一个简单的测试:
我从服务器发送从 1 到 30000 的数字:

char* buf = (char*)malloc(BUFLEN);          
for( int i = 0;i < 30000;++i ){
     ZeroMemory(buf, BUFLEN);
     itoa(i+1, buf, 10);
     send(current_client, buf, BUFLEN, 0);
}
free(buf);

客户端接收并保存:

char *buf = (char*)malloc(DEFAULT_BUFLEN);
ofstream out;
out.open(filename, ios::binary);
for( int i = 0;i < 30000;++i ){
     ZeroMemory(buf, DEFAULT_BUFLEN);
             recv(ConnectSocket, buf, DEFAULT_BUFLEN, 0);
             out.write(buf, DEFAULT_BUFLEN);
             out << endl;
}
out.close();
free(buf);

我希望在文件中看到类似的内容:
1
2
3
4
...
30000
但是有一些额外的数据包,包含“/0”,被传输并且文件看起来像这样:
1
2
3

4
5

6
...
2600
如果我尝试跳过“/0”数据包,来自服务器的数据也会像这样跳过:
1
2
3
4
5
6
9 ...
2600
我做错了什么?

【问题讨论】:

    标签: c++ winsock


    【解决方案1】:

    recv() 告诉您实际收到了多少字节。即使没有完全填满,您也会忽略输出整个缓冲区的值。在服务器端也是一样 - 你发送的数据比你格式化的多。

    您也没有考虑到send() 和recv() 可以发送/接收的字节数少于您请求的字节数。

    试试这个:

    bool sendraw(SOCKET socket, void *buf, int buflen)
    {
        unsigned char *p = (unsigned char*) buf;
        while (buflen > 0)
        {
            int sent = send(socket, p, buflen, 0);
            if (sent < 1) return false;
            p += sent;
            buflen -= sent;
        }
        return true;
    }   
    
    for( int i = 0; i < 30000;++i )
    {   
        int j = htonl(i+1);
        if (!sendraw(current_client, &j, sizeof(int)))
            break;
    }   
    

    .

    bool recvraw(SOCKET socket, void *buf, int buflen)
    {
        unsigned char *p = (unsigned char*) buf;
        while (buflen > 0)
        {
            int received = recv(socket, p, buflen, 0);
            if (received < 1) return false;
            p += received;
            buflen -= received;
        }
        return true;
    }   
    
    ofstream out; 
    out.open(filename, ios::binary); 
    for( int i = 0; i < 30000;++i )
    { 
        int j;
        if (!recvraw(ConnectSocket, &j, sizeof(int)))
            break;
        out << ntohl(j) << endl; 
    } 
    out.close(); 
    

    【讨论】:

      【解决方案2】:

      来自recv的manual page:

      对于面向连接的套接字(例如 SOCK_STREAM 类型),调用 recv 将返回与当前可用的数据一样多的数据——最大为指定的缓冲区大小。

      这意味着recv 可能会给你不完整的数据包。如果数据包有固定长度或终止符,则需要在附加到缓冲区的循环中调用recv,直到收到所有数据包。然而,这带来了另一个问题,即第二次调用recv 可能会给您最后一个数据包的剩余部分,以及另一个数据包的一部分。

      在您的情况下,发送方的发送速度可能比接收方接收它们的速度快,在这种情况下,recv 将阻塞(如果套接字阻塞)或返回错误(如果套接字非阻塞) .您必须检查从recv 返回的值才能知道它是什么。如果套接字是非阻塞的,那么recv 将返回SOCKET_ERROR(即-1)并且WSAGetLastError 将返回WSAEWOULDBLOCK。

      【讨论】:

      • 有没有办法让发件人的速度足够慢,但不要太多,因为对于 31 MB 的文件,62 374 个数据包的 15 毫秒延迟实在是太多了。如果在我发送每个数据包之后超过 60 毫秒的“下一个”请求。如果我必须在保存之前接收整个文件,这是否意味着我需要 31 mb 缓冲区?
      • @slaviber 为什么你要发送“下一个”请求? TCP 是一种流协议。你正在杀死它。您不需要放慢速度,您需要加快系统速度。
      • @slaviber 对于 TCP 上的文件传输,要么使用“下一个”数据包来告诉发送者发送下一个数据包。 或者你可以让发送者先单独发送文件的size,这样接收者就会知道需要多少字节。然后你尽可能多地接收(直到你收到所有东西)并立即写入磁盘。没有大的缓冲区。即使这个问题是 6 年前的问题,即便如此,31MB 的缓冲区也不会太多(除非在嵌入式系统上)。
      【解决方案3】:

      我发现了问题所在。
      正如 Joachim 和 Remy 所说,有时客户端不会收到整个缓冲区,所以我稍微修改了代码:
      从服务器端:

                   sent = 0;
                   while( sent != BUFLEN ){
                          sent += send(current_client, (buf+sent), BUFLEN-sent, 0);
                   }
      

      和客户:

                   recvd = 0;  
                   while( recvd != DEFAULT_BUFLEN ){  
                          recvd += recv(ConnectSocket, (buf+recvd), DEFAULT_BUFLEN-recvd, 0);  
                   }  
      

      而且我知道缓冲区没有完全填满,通常我将二进制数据作为 char* 发送,而不仅仅是整数。

      【讨论】:

      • 这是严重不足的。您需要对每个send() 和recv() 进行错误检查。目前,如果出现错误或流过早结束,您将永远循环。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      • 1970-01-01
      • 1970-01-01
      • 2011-11-06
      • 1970-01-01
      • 2015-04-08
      • 2021-10-10
      相关资源
      最近更新 更多