2011-08-29 35 views
3

我在使用winsock讀取分塊HTTP響應數據時遇到了問題。 我發送請求罰款,並得到如下回:使用Winsock接收分塊HTTP數據

HTTP/1.1 200 OK 
Server: LMAX/1.0 
Content-Type: text/xml; charset=utf-8 
Transfer-Encoding: chunked 
Date: Mon, 29 Aug 2011 16:22:19 GMT 

用winsock的recv。但是現在它只是掛起。我讓聽衆在無限循環中運行,但沒有任何東西可以被接收。

我認爲這是一個C++問題,但它也可能與我通過stunnel推送連接將其封裝在HTTPS中的事實有關。我有一個測試應用程序,使用C#中的某些庫,它可以通過stunnel完美地工作。我很困惑,爲什麼我的循環在初始recv後沒有收到C++分塊數據。

這是有問題的循環......它上面的分塊OK響應後調用...

while(true) 
{ 
    recvBuf= (char*)calloc(DEFAULT_BUFLEN, sizeof(char)); 
    iRes = recv(ConnectSocket, recvBuf, DEFAULT_BUFLEN, 0); 
    cout << WSAGetLastError() << endl; 
    cout << "Recv: " << recvBuf << endl; 
    if (iRes==SOCKET_ERROR) 
    { 
     cout << recvBuf << endl; 
     err = WSAGetLastError(); 
     wprintf(L"WSARecv failed with error: %d\n", err); 
     break; 
    }  

} 

任何想法?

+0

我建議你改變你的代碼不要在循環中分配,否則你一次泄漏內存,一個DEFAULT_BUFLEN。另外,循環的停止條件是什麼?你可能在達到這個'recv'之前消耗了數據嗎? – Hasturkun

+0

是的,我知道它正在泄漏記憶,但我現在對此沒有太大的興趣。我可以輕鬆地將它切換到memset。我在每次收到後都會打印一張,表明數據永遠不會到達。 –

+0

如果您在該位之前發佈代碼,可能會有所幫助,以查看您是否意外地使用了該數據。另外請注意,如果'recv'返回一個錯誤,'recvBuf'永遠不會被修改,所以打印是毫無意義的。 – Hasturkun

回答

5

您需要更改您的閱讀代碼。您不能像使用固定長度緩衝區一樣讀取chunked數據。數據以可變長度的塊發送,其中每個塊都有一個標題,以字節爲單位指定塊的實際長度,最後一塊數據的長度爲0.您需要讀取塊標題才能正確處理塊。請閱讀RFC 2616 Section 3.6.1。您的邏輯需要更像以下僞代碼:

send request; 

status = recv() a line of text until CRLF; 
parse status as needed; 
response-code = extract response-code from status; 

do 
{ 
    line = recv() a line of text until CRLF; 
    if (line is blank) 
     break; 
    store line in headers list; 
} 
while (true); 

parse headers list as needed; 

if ((response-code is not in [1xx, 204, 304]) and (request was not "HEAD")) 
{ 
    if (Transfer-Encoding header is present and not "identity") 
    { 
     do 
     { 
      line = recv a line of text until CRLF; 
      length = extract length from line; 
      extensions = extract extensions from line; 
      process extensions as needed; // optional 
      if (length == 0) 
       break; 
      recv() length number of bytes into destination buffer; 
      recv() and discard bytes until CRLF; 
     } 
     while (true); 

     do 
     { 
      line = recv a line of text until CRLF; 
      if (line is blank) 
       break; 
      store line in headers list as needed; 
     } 
     while (true); 

     re-parse headers list as needed; 
    } 
    else if (Content-Length header is present) 
    { 
     recv() data into destination buffer up to Content-Length number of bytes; 
    } 
    else if (Content-Type header starts with "multipart/") 
    { 
     recv() data into destination buffer until MIME terminator derived from the Content-Type's "boundary" attribute value is reached; 
    } 
    else 
    { 
     recv() data into destination buffer until disconnected; 
    } 
} 
0

事實上,您沒有收到分塊,但內容被分塊。你必須爲自己畫一張圖片,看看你收到的任何緩衝區的樣子。這不像你當時收到一塊。有時你有一些前面塊的數據,這一行表示新塊的大小,後面跟着一些塊數據。有些時候你只是收到一些塊數據。另一次有一些塊數據和一部分指示新塊的部分等等。想象一下最壞的情況,這並不容易。閱讀此:http://www.jmarshall.com/easy/http/

在您可以使用下面的一段代碼之前收到所有的標題,直到空行。內容在緩衝區中的起始位置是nContentStart。代碼使用一些我無法分享的內部類,但你應該明白;)就我測試而言,它的工作方式與預期相同,並且不會泄漏內存。雖然這不容易,但我不能完全確定!

if (bChunked) 
    { 
     int nOffset = nContentStart; 
     int nChunkLen = 0; 
     int nCopyLen; 

     while (true) 
     { 
      if (nOffset >= nDataLen) 
       {pData->SetSize(0); Close(); ASSERTRETURN(false);} 

      // copy data of previous chunk to caller's buffer 

      if (nChunkLen > 0) 
      { 
       nCopyLen = min(nChunkLen, nDataLen - nOffset); 
       n = pData->GetSize(); 
       pData->SetSize(n + nCopyLen); 
       memcpy(pData->GetPtr() + n, buf.GetPtr() + nOffset, nCopyLen); 
       nChunkLen -= nCopyLen; 
       ASSERT(nChunkLen >= 0); 

       nOffset += nCopyLen; 
       if (nChunkLen == 0) 
        nOffset += strlen(lpszLineBreak); 
       ASSERT(nOffset <= nDataLen); 
      } 

      // when previous chunk is copied completely, process new chunk 

      if (nChunkLen == 0 && nOffset < nDataLen) 
      { 
       // chunk length is specified on first line 

       p1 = buf.GetPtr() + nOffset; 
       p2 = strstr(p1, lpszLineBreak); 

       while (!p2) // if we can't find the line break receive more data until we do 
       { 
        buf.SetSize(nDataLen + RECEIVE_BUFFER_SIZE + 1); 
        nReceived = m_socket.Receive((BYTE*)buf.GetPtr() + nDataLen, RECEIVE_BUFFER_SIZE); 

        if (nReceived == -1) 
         {pData->SetSize(0); Close(); ASSERTRETURN(false);} // connection error 
        if (nReceived == 0) 
         {pData->SetSize(0); Close(); ASSERTRETURN(false);} // all data already received but did not find line break 

        nDataLen += nReceived; 
        buf[nDataLen] = 0; 

        p1 = buf.GetPtr() + nOffset; // address of buffer likely changed 
        p2 = strstr(p1, lpszLineBreak); 
       } 

       *p2 = 0; 
       p2 += strlen(lpszLineBreak); 

       p3 = strchr(p1, ';'); 
       if (p3) 
        *p3 = 0; 

       if (sscanf(p1, "%X", &nChunkLen) != 1) 
        {pData->SetSize(0); Close(); ASSERTRETURN(false);} 

       if (nChunkLen < 0) 
        {pData->SetSize(0); Close(); ASSERTRETURN(false);} 

       if (nChunkLen == 0) 
        break; // last chunk received 

       // copy the following chunk data to caller's buffer 

       nCopyLen = min(nChunkLen, buf.GetPtr() + nDataLen - p2); 
       n = pData->GetSize(); 
       pData->SetSize(n + nCopyLen); 
       memcpy(pData->GetPtr() + n, p2, nCopyLen); 
       nChunkLen -= nCopyLen; 
       ASSERT(nChunkLen >= 0); 

       nOffset = (p2 - buf.GetPtr()) + nCopyLen; 
       if (nChunkLen == 0) 
        nOffset += strlen(lpszLineBreak); 

       if (nChunkLen == 0 && nOffset < nDataLen) 
        continue; // a new chunk starts in this buffer at nOffset, no need to receive more data 
      } 

      // receive more data 

      buf.SetSize(RECEIVE_BUFFER_SIZE + 1); 
      nDataLen = m_socket.Receive((BYTE*)buf.GetPtr(), RECEIVE_BUFFER_SIZE); 
      if (nDataLen == -1) 
       {pData->SetSize(0); Close(); ASSERTRETURN(false);} 
      if (nDataLen == 0) 
       {pData->SetSize(0); Close(); ASSERTRETURN(false);} 
      buf[nDataLen] = 0; 

      nOffset = 0; 
     } 

     // TODO: receive optional footers and add them to m_headers 
    }