使用 Winsock 接收分块 HTTP 数据

发布于 2024-12-01 20:19:45 字数 942 浏览 1 评论 0原文

我在使用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 完美运行。我很困惑为什么我的循环在初始接收后没有接收到 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;
    }     

}

有什么想法吗?

I'm having trouble reading in some chunked HTTP response data using winsock.
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. 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. I have a test application using some libs in C# which works perfectly through stunnel. I'm confused as to why my loop is not receiving the C++ chunked data after the initial recv.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

能怎样 2024-12-08 20:19:45

您需要更改您的阅读代码。您无法像您尝试那样使用固定长度缓冲区读取分块数据。数据以可变长度块的形式发送,其中每个块都有一个标头,该标头指定该块的实际长度(以字节为单位),并且数据的最终块的长度为 0。您需要读取分块标头才能正确处理块。请阅读 RFC 2616 第 3.6.1 节。您的逻辑需要更像以下伪代码:

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/")
    {
        boundary = extract boundary from Content-Type's "boundary" attribute;
        recv() data into destination buffer until MIME termination boundary 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;

You need to change your reading code. You cannot read chunked data using a fixed-length buffer like you are trying to do. 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. Please read RFC 2616 Section 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/")
    {
        boundary = extract boundary from Content-Type's "boundary" attribute;
        recv() data into destination buffer until MIME termination boundary 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;
┊风居住的梦幻卍 2024-12-08 20:19:45

事实上,您没有收到分块的内容,但内容已分块。您必须为自己绘制一幅您收到的缓冲区的外观图。这并不像你当时收到一大块。有时您有前一个块的一些数据,指示新块大小的行,后面跟着一些块数据。有时您只收到一点块数据。另一次是一些块数据和指示新块的行的一部分,等等。想象一下最坏的情况,这并不容易。阅读此内容: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
    }

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/

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. 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
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文