简体   繁体   English

使用 Winsock 接收分块的 HTTP 数据

[英]Receiving Chunked HTTP Data With Winsock

I'm having trouble reading in some chunked HTTP response data using winsock.我在使用 winsock 读取一些分块的 HTTP 响应数据时遇到问题。 I send a request fine and get the following back:我发送了一个请求并得到以下回复:

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

using winsock recv.使用 winsock 接收。 At this point however it just hangs.然而此时它只是挂起。 I have the listener running in an infinite loop but nothing is ever picked up.我让监听器在无限循环中运行,但没有任何东西被拾取。

I think it's a C++ issue but it could also be related to the fact that I pushing the connection through stunnel to wrap it up inside HTTPS.我认为这是一个 C++ 问题,但也可能与我通过 stunnel 推送连接以将其封装在 HTTPS 中的事实有关。 I have a test application using some libs in C# which works perfectly through stunnel.我有一个使用 C# 中的一些库的测试应用程序,它通过 stunnel 完美运行。 I'm confused as to why my loop is not receiving the C++ chunked data after the initial recv.我很困惑为什么我的循环在初始接收后没有收到 C++ 分块数据。

This is the loop in question...it is called after the chunked ok response above...这是有问题的循环……它是在上面的分块确定响应之后调用的……

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;
    }     

}

Any ideas?有任何想法吗?

You need to change your reading code.您需要更改阅读代码。 You cannot read chunked data using a fixed-length buffer like you are trying to do.您无法像尝试那样使用固定长度的缓冲区读取chunked数据。 The data is sent in variable-length chunks, where each chunk has a header that specifies the actual length of the chunk in bytes, and the final chunk of the data has a length of 0. You need to read the chunked headers in order to process the chunks properly.数据以可变长度的块发送,其中每个块都有一个标头,以字节为单位指定块的实际长度,数据的最后一个块的长度为 0。您需要读取分块的标头以便正确处理块。 Please read RFC 2616 Section 3.6.1 .请阅读RFC 2616 第 3.6.1 节 Your logic needs to be more like the following pseudo-code:您的逻辑需要更像以下伪代码:

send request;

status = recv() a line of text until CRLF;
parse status as needed;
response-code = extract response-code from status;
response-version = extract response-version 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() Content-Length number of bytes into destination buffer;
    }
    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;
    }
}

if (not disconnected)
{
    if (response-version is "HTTP/1.1")
    {
        if (Connection header is "close")
            close connection;
    }
    else
    {
        if (Connection header is not "keep-alive")
            close connection;
    }
}

check response-code for errors;
process destination buffer, per info in headers list;

Indeed you do not receive chunked, but the content is chunked.事实上,你没有收到分块,但内容是分块的。 You have to draw a picture for yourself how any buffer you receive might look.您必须为自己绘制一张图片,您收到的任何缓冲区可能看起来如何。 It's not like you receive one chunk at the time.这不像你一次收到一大块。 Sometimes you have some data of the previous chunk, the line indicating the size of the new chunk, followed by some chunk data.有时你有前一个块的一些数据,指示新块大小的行,后面是一些块数据。 Some other time you just receive just a bit of chunk data.其他时候你只收到一点块数据。 Another time a bit of chunk data and a part of the line indicating the new chunk, etc, etc. Imagine the worst case scenarios, this isn't easy.另一次是一些块数据和一部分指示新块的行,等等。想象一下最坏的情况,这并不容易。 Read this: http://www.jmarshall.com/easy/http/阅读: http : //www.jmarshall.com/easy/http/

Before you can use the following piece of code receive all the headers until the empty line.在您可以使用以下代码之前,接收所有标题直到空行。 Where the content starts in the buffer is nContentStart .缓冲区中内容的起始位置是nContentStart The code uses some in-house classes I cannot share but you should get the idea ;) As far as I tested it works like expected and does not leak memory.该代码使用了一些我无法共享的内部类,但您应该明白这一点;) 据我测试,它按预期工作并且不会泄漏内存。 Although since this isn't easy I cannot be completely sure!虽然这并不容易,但我不能完全确定!

    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
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM