下载文件,winsock recv() 到 fstream 写入,文件损坏

Posted

技术标签:

【中文标题】下载文件,winsock recv() 到 fstream 写入,文件损坏【英文标题】:Download file, winsock recv() to fstream write, file corrupted 【发布时间】:2014-11-30 14:11:50 【问题描述】:

我正在尝试使用 winsock 从我的网站下载文件。我遇到了无数的问题,现在我能够下载文件,但它已损坏。

它不适用于任何文件扩展名。文本和图片最终损坏,音频文件也是如此。对于二进制文件,我可以在执行“程序太大而无法放入内存”时看到此错误。

首先,我向服务器发送()一个 Head 请求以了解内容长度(要下载的文件的大小),然后我发送一个 Get 请求并将我接收到缓冲区中。 recv 完成后,我编写文件。

我尝试在这里编写一个简单的代码示例,我尝试了各种循环方法,但最后我仍然有一个损坏的文件写入磁盘。大小相同(服务器上的 50kb 文件,下载并写入磁盘的 50kb 文件)。 谢谢大家。

headrequest = "HEAD " + "/folder/file.asd" + " HTTP/1.1\r\nHost: " + "url.com" + "\r\n\r\n";
getrequest = "GET " + "/folder/file.asd" + " HTTP/1.1\r\nHost: " + "url.com" + "\r\n\r\n";

send(socket, headrequest, sizeof(headrequest), 0);
recv(socket, reply_buf_headrequest, sizeof(reply_buf_headrequest), 0); 
//two functions to get the header end and "Content-Lenght" data from header

send(socket, getrequest, sizeof(getrequest), 0);
while(1)
    
 recv(socket, recvbuff, sizeof(recvbuff), 0);
 if (recv(socket, recvbuff, sizeof(recvbuff), 0) == 0) 
  break; 

out.write(recvbuff, content_lenght); // also tried --> out.write(recvbuff + header_end, content_lenght) //same errors.
out.close();

我搞砸了缓冲区/位置以开始读/写或类似的东西。我认为使用 recvbuff + header_end 会起作用,因为它会从标题的末尾开始读取以获取文件。这令人困惑。 我希望一个善良的灵魂可以帮助我弄清楚如何处理这种情况并正确写入文件字节。 :)

编辑:

我认为我正在覆盖这样的数据。该死。 content_length 来自之前的 HEAD 请求,一个函数读取接收到的数据并找到“Content-Length”值,即 /folder/file.asd 的字节大小。 我无法在 Get 请求中得到它,所以我这样做了.. 它得到的文件大小是正确的。

所以,

while(1)

  if (recv(socket, recvbuff, sizeof(recvbuff), 0) == 0)
   break;

out.write(recvbuff, content_lenght);
out.close();

out.write 应该在循环之后还是在 while(1) 循环内?

感谢您的快速回复。 :)

我省略了错误检查部分以保持示例代码简短,抱歉。 头部和获取请求是字符,我也尝试过使用字符串,但最终没有使用 sizeof() 。直到明天我才能访问真正的代码,所以我尝试在家里使用类似的 sn-p 修复它。可能有一些错别字..

编辑 2: 使用一个小 exe 进行测试,该 exe 使用比文件大的缓冲区生成消息框即时消息,并且:

ofstream out("test.exe", ios::binary);

现在使用这个循环:

    int res;   // return code to monitor transfer
do     
    res = recv(socket, recvbuff, sizeof(recvbuff), 0);   // look at return code
    if (res > 0)  // if bytes received 
        out.write(recvbuff, res ); // write them  
 while (res>0);   // loop as long as we receive something  
if (res==SOCKET_ERROR)  
    cerr << "Error: " << WSAGetLastError() << endl; 

执行时仍然出现“程序太大而无法放入内存”错误..

【问题讨论】:

headrequest 和 getrequest 的数据类型是什么?如果它们是 std::string 或类似的字符串类,则 sizeof(...) 不会返回字符串长度。 您从不检查 send() 或 recv() 返回的值,因此您不知道实际发送了多少字节,也不知道实际将多少字节放入 recvbuff。它可能比您请求的字节数少,在这种情况下,您的缓冲区将不会被完全填充。 除了发布的解决方案之外,请确保您以二进制模式打开输出文件,否则将翻译换行符。 您的编辑仍然无法工作:recv() 将始终在缓冲区的开头写入它收到的内容。在循环结束时,您不断覆盖缓冲区,并且缓冲区仅包含最后几个字节。只有这样你才写缓冲区,但是对于文件的全长(可能会超出缓冲区大小)所以你会写很多未初始化的数据! 在发送GET 之前,您不需要使用HEAD 来获取文件大小。 HTTP 请求/响应是自包含的消息。对GET 的响应告诉您如何在下载文件时确定文件的大小,无论是通过Content-Length 标头、Transfer-Encoding: chunked 标头等。 HTTP比你认为的要复杂得多(而且你的套接字读/写代码通常是完全错误的),所以你最好使用 WinInet/WinHTTP 或像 libcurl 这样的第三方库来处理所有这些细节给你。 【参考方案1】:

这很正常!您的代码并没有真正处理您收到的内容!

看我的cmets:

while(1)  // Your original (indented) code commented: 
    
    recv(socket, recvbuff, sizeof(recvbuff), 0);  // You read data in buffer 
    if (recv(socket, recvbuff, sizeof(recvbuff), 0) == 0)  // you read again, overwriting data you've received !! 
        break; 

out.write(recvbuff, content_lenght); // You only write the last thing you've received. 
                            // Where does the lengthe come from ?  Maybe you have buffer overflow as well.

如下重写你的循环:

int res;   // return code to monitor transfer
do     
    res = recv(socket, recvbuff, sizeof(recvbuff), 0);   // look at return code
    if (res > 0)  // if bytes received 
        out.write(recvbuff, res ); // write them  
 while (res>0);   // loop as long as we receive something  
if (res==SOCKET_ERROR)  
    cerr << "Error: " << WSAGetLastError() << endl; 

优点是您不必关心整体大小,因为您编写收到的每个小块。

编辑:

在我们交换意见后,这里有一些额外的信息。正如有人指出的那样,HTTP 协议管理起来有些复杂。有关响应格式的更多详细信息以及您必须跳过的标头,请参阅here, in chapter 6。

这里有一些更新的概念证明来跳过标题:

ofstream out;
out.open(filename, ios::binary);
bool header_skipped=false;  // was header skiped (do it only once !!) 
int res;   // return code to monitor transfer
do 
    res = recv(mysocket, recvbuff, sizeof(recvbuff), 0);   // look at return code
    if (res > 0)     // if bytes received
    
        size_t data_offset = 0;      // normally take data from begin of butter 
        if (!header_skipped)     // if header was not skipped, look for its end
            char *eoh = "\r\n\r\n";
            auto it = search (recvbuff, recvbuff + res, eoh, eoh + 4); 
            if (it != recvbuff + res)    // if header end found: 
                data_offset = it - recvbuff + 4;      // skip it
                header_skipped = true;              // and then do not care any longer
                                         // because data can also containt \r\n\r\n
        
        out.write(recvbuff + data_offset, res - data_offset); // write, ignoring before the offset
    
 while (res > 0);   // loop as long as we receive something  
if (res == SOCKET_ERROR) cerr << "Error: " << WSAGetLastError() << endl;
out.close();

注意! 如前所述,这是一个概念证明。它可能会起作用。但是,请注意,您无法确定数据将如何在接收方重新组合。很可能标头的结尾在两个连续读取之间拆分(例如,\r 作为一个 recv() 的最后一个字节,\n\r\n 作为下一个recv() 的第一个字节)。在这种情况下,这个简单的代码将找不到它。所以它还不是生产质量代码。由你来进一步改进

【讨论】:

尝试了您在此处编写的循环。没有返回错误,文件被下载并以完全相同的大小写入,但在执行时它抛出“程序太大而无法放入内存”。我试图下载一个小 exe 文件,只弹出一个 MessageBox 进行测试。 好的!正如 Oblivious 船长指出的那样,您必须确保文件以二进制形式打开,以避免某些二进制字符被转换并产生问题。 我用这个来处理文件ofstream out("test.exe", ios::binary),错了吗?感谢您的耐心等待。 好的,使用你的循环,我现在每次都得到 2 个额外的字节......以及“太大而无法放入内存错误”......我该怎么办? windows xp 出现内存错误,在较高版本中我收到“不支持的 16 位应用程序”错误。尝试使用 32 位和 64 位 Windows 机器。

以上是关于下载文件,winsock recv() 到 fstream 写入,文件损坏的主要内容,如果未能解决你的问题,请参考以下文章

Winsock recv() 函数阻塞其他线程

使用 Winsock 的 send()/recv() 时是不是需要确认响应?

Winsock recv() 不阻塞

Winsock“recv”不会在非正常连接终止时返回

winsock套接字的recv函数

当调用winsock 中的recv 函数并且没有收到所有数据时会发生啥?