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

时间:2014-11-30 14:11:51

标签: c++ windows http visual-c++ winsock

我试图使用winsock从我的网站下载文件。我面临无数问题,现在无法下载文件,但它已损坏。

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

首先我向服务器发送()一个Head请求以了解内容文件(要下载的文件的大小),然后我发送一个Get请求并将i recv放入缓冲区。完成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请求,函数读取recv的数据并找到" Content-Length" value,它是/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()。我无法访问真正的代码,直到明天,所以我试图在家里使用类似的片段修复它...有可能是一些错别字..

编辑2: 作为一个小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; 

仍然有#34;程序太大而无法适应内存&#34;执行时出错...

1 个答案:

答案 0 :(得分:1)

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

见我的评论:

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()的第一个字节)。在这种情况下,这个简单的代码将找不到它。所以它还没有生产质量代码。由你来进一步改进