簡體   English   中英

無法使用 boost asio read() 獲取所有數據

[英]Unable to get all the data with boost asio read()

我正在開發一個 RTSP 客戶端。 它是一個簡單的基於文本的協議。 服務器向我發送了一個響應,我可以在 Wireshark 上看到該響應,但是當我嘗試閱讀它時並沒有得到所有響應。

發送的數據是這樣的(來自wireshark):

RTSP/1.0 200 OK
CSeq: 1
Date: Thu, Aug 11 2022 12:09:54 GMT
Content-Base: rtsp://10.45.231.24:559/pint.sdp/
Content-Type: application/sdp
Content-Length: 590

v=0
o=- 1660140466387175 1 IN IP4 10.1.23.23
s=Session streamed by Pinkman
i=pint.sdp
t=0 0
a=tool:LIVE555 Streaming Media v2017.10.28
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:Session streamed by Pinkman
a=x-qt-text-pint.sdp
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:2000
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=640015;sprop-parameter-sets=Z2QAFazkBQfoQAAAAwBAAAAPA8WLRIA=,aOvssiw=
a=control:track1
m=application 0 RTP/AVP 97
c=IN IP4 0.0.0.0
b=AS:1000
a=rtpmap:97 vnd.onvif.metadata/10000
a=control:track2

我的閱讀策略是首先“read_until”內容長度,然后使用內容長度讀取數據的 rest。

相反,當我嘗試它時,我只得到了大約一半的數據。 第二次讀取只是掛起。 我認為這與套接字緩沖區的大小有關,但事實並非如此。

這是代碼:

         boost::asio::streambuf receive_buffer;
         std::size_t bytes_transferred;
         receive_buffer.prepare(2048);
         std::string delimeter{ "\r\n\r\n" };
         std::string message = "DESCRIBE " + m_rtspServerURI + " RTSP/1.0\nCSeq: 1\r\n\r\n";      
         boost::asio::write(*m_socket, boost::asio::buffer(message), error);
         if (error)
            return false;
         std::this_thread::sleep_for(std::chrono::milliseconds(maxResponseWaitingTime_ms));   
         
         
         bytes_transferred = boost::asio::read_until(*m_socket, receive_buffer, delimeter.c_str(), error);
         if (error && error != boost::asio::error::eof)
         {
             std::cout << "receive failed: " << error.message() << std::endl;
             return false;
         }
         else
         {
             std::cout << "\n Bytes Transferred :: " << bytes_transferred << "\n";
             std::string describeHeaders{
                 boost::asio::buffers_begin(receive_buffer.data()),
                 boost::asio::buffers_begin(receive_buffer.data()) + 
                 bytes_transferred - delimeter.size() };
             
             receive_buffer.consume(bytes_transferred);

             std::size_t sdpSize = extractContentLength(describeHeaders);

             std::cout << "Describe Headers:: \n" << describeHeaders << "\n\n" << "Sdp Size: " << sdpSize << "\n";

             
             bytes_transferred = boost::asio::read(*m_socket, receive_buffer,
                 boost::asio::transfer_exactly(sdpSize), error);

             std::string sdpInfo{
                 boost::asio::buffers_begin(receive_buffer.data()),
                 boost::asio::buffers_begin(receive_buffer.data()) + bytes_transferred };

             receive_buffer.consume(bytes_transferred);


             std::cout << "sdpinfo :: \n" << sdpInfo << "\n";

我究竟做錯了什么?

內容長度錯誤。 它是 580 字節。 您將無限期地等待另外 10 個字節。

此外,當使用transfer_exactly時,已經緩沖的字節不計算在內。 這是不幸的:

#include <boost/asio.hpp>
#include <iostream>

namespace net = boost::asio;

using boost::system::error_code;

struct DummyStream {
    size_t read_some(auto) const { return 0; }
    size_t read_some(auto, error_code& ec) const { ec = {}; return 0; }
};

int main() {
    net::io_context io;
    error_code      ec;
    DummyStream     s;

    net::streambuf buf;
    std::ostream(&buf) << "HEADERS\r\n\r\nBODY";

    auto n = net::read_until(s, buf, "\r\n\r\n", ec);

    std::cout << "n:" << n << " ec:" << ec.message() << " buf.size():" << buf.size() << "\n";

    buf.consume(n);
    n = net::read(s, buf, net::transfer_exactly(1), ec); // not even 1
    std::cout << "n:" << n << " ec:" << ec.message() << " buf.size():" << buf.size() << "\n";
}

即使是這個極小化的測試程序也會掛起,因為transfer_exactly(1)沒有考慮緩沖區中已經存在的 4 個剩余字節。

相反,您應該創建一個將現有緩沖區內容考慮在內的功能完成條件,或者手動進行數學運算:

住在科利魯

static constexpr auto expected_content_length = 4;
buf.consume(n);
n = buf.size();
if (n < expected_content_length) {
    n += net::read(s, buf,
                   net::transfer_exactly(expected_content_length - n), ec);
}

現在它正確打印:

n:11 ec:Success buf.size():15
n:4 ec:Success buf.size():4

申請

我個人傾向於使用現有的解析庫。 遺憾的是,RTSP 不是標准 HTTP,所以我破解了一個基於 Beast 的實用程序 function:

#include <boost/beast/http.hpp>
size_t extractContentLength(std::string_view res) {
    namespace http = boost::beast::http;
    http::response_parser<http::empty_body> p;
    p.eager(false);
    error_code ec;
    // replacing non-HTTP status line
    if (!ec || ec == http::error::need_more) p.put(net::buffer("HTTP/1.1 200 OK\r\n", 17), ec);
    if (!ec || ec == http::error::need_more) p.put(net::buffer(res.substr(res.find_first_of("\n") + 1)), ec);
    //if (!ec || ec == http::error::need_more) p.put(net::buffer("\r\n\r\n", 4), ec);
    assert(p.is_header_done());

    auto const& msg = p.get();
    return msg.has_content_length()
        ? std::stoull(msg.at(http::field::content_length))
        : 0;
}

在這里,我們通過用虛擬 HTTP 狀態行替換 RTSP 狀態行來安撫 HTTP 解析器。 然后我們可以使用現有的頭部解析和驗證來提取內容長度 header 字段值。

現在,我們可以實現您的 function 工作:

科利魯

#undef NDEBUG
#define REPRO
#include <boost/asio.hpp>
#include <fstream>
#include <iostream>

namespace net = boost::asio;

using boost::system::error_code;
using net::ip::tcp;
using net::buffers_begin;

using namespace std::chrono_literals;

static std::string const m_rtspServerURI           = "/stream";
static auto const        maxResponseWaitingTime_ms = 100ms;

extern std::string const sample590;
extern std::string const sample580;

struct DummyStream {
    size_t read_some(auto) const { return 0; }
    size_t read_some(auto, error_code& ec) const { ec = {}; return 0; }
};

#include <boost/beast/http.hpp>
size_t extractContentLength(std::string_view res) {
    namespace http = boost::beast::http;
    http::response_parser<http::empty_body> p;
    p.eager(false);
    error_code ec;
    // replacing non-HTTP status line
    if (!ec || ec == http::error::need_more) p.put(net::buffer("HTTP/1.1 200 OK\r\n", 17), ec);
    if (!ec || ec == http::error::need_more) p.put(net::buffer(res.substr(res.find_first_of("\n") + 1)), ec);
    //if (!ec || ec == http::error::need_more) p.put(net::buffer("\r\n\r\n", 4), ec);
    assert(p.is_header_done());

    auto const& msg = p.get();
    return msg.has_content_length()
        ? std::stoull(msg.at(http::field::content_length).data())
        : 0;
}

bool foo(std::string_view sample) {
    net::io_context io;
    error_code ec;
    std::string_view const delimiter{"\r\n\r\n"};

#ifdef REPRO
    DummyStream s;
    auto* m_socket = &s;
#else
    tcp::socket s(io);
    auto* m_socket = &s;

    s.connect({{}, 80});
    std::string const message =
        "DESCRIBE " + m_rtspServerURI + " RTSP/1.0\nCSeq: 1\r\n\r\n";

    net::write(*m_socket, net::buffer(message), error);
#endif
    if (ec)
        return false;

    net::streambuf receive_buffer;
#ifdef REPRO
    //std::ostream(&receive_buffer)
        //<< std::ifstream("input.txt", std::ios::binary).rdbuf();
    std::ostream(&receive_buffer) << sample;
#else
    receive_buffer.prepare(2048);
#endif

    std::this_thread::sleep_for(maxResponseWaitingTime_ms);

    auto bytes_transferred =
        net::read_until(*m_socket, receive_buffer, delimiter, ec);

    if (ec && ec != net::error::eof) {
        std::cout << "receive failed: " << ec.message() << std::endl;
        return false;
    } else {
        std::cout << "Bytes Transferred: " << bytes_transferred << " ("
                  << ec.message() << ")" << std::endl;

        auto consume = [&receive_buffer](size_t n) {
            auto b = buffers_begin(receive_buffer.data()), e = b + n;
            assert(n <= receive_buffer.size());

            auto s = std::string{b, e};
            receive_buffer.consume(n);
            return s;
        };

        auto headers = consume(bytes_transferred);

        size_t sdpSize = extractContentLength(headers);

        std::cout << "Describe Headers: " << headers << "Sdp Size: " << sdpSize
                  << std::endl;

        bytes_transferred = receive_buffer.size();
        std::cout << "Pre-buffer: " << bytes_transferred << std::endl;
        if (bytes_transferred < sdpSize) {
            bytes_transferred += net::read(
                *m_socket, receive_buffer,
                net::transfer_exactly(sdpSize - bytes_transferred), ec);
        }

        auto sdpInfo = consume(bytes_transferred);

        std::cout << "sdpinfo: " << sdpInfo << "\n";

        // note theoretically receive_buffer may still contain data received
        // *after* the body
    }
    return true;
}

int main() {
    foo(sample580);
    //foo(sample590); // would hang
}

std::string const sample590 =
    "RTSP/1.0 200 OK\r\nCSeq: 1\r\nDate: Thu, Aug 11 2022 12:09:54 "
    "GMT\r\nContent-Base: rtsp://10.45.231.24:559/pint.sdp/\r\nContent-Type: "
    "application/sdp\r\nContent-Length: 590\r\n\r\nv=0\r\no=- 1660140466387175 "
    "1 IN IP4 10.1.23.23\r\ns=Session streamed by Pinkman\r\ni=pint.sdp\r\nt=0 "
    "0\r\na=tool:LIVE555 Streaming Media "
    "v2017.10.28\r\na=type:broadcast\r\na=control:*\r\na=range:npt=0-\r\na=x-"
    "qt-text-nam:Session streamed by "
    "Pinkman\r\na=x-qt-text-pint.sdp\r\nm=video 0 RTP/AVP 96\r\nc=IN IP4 "
    "0.0.0.0\r\nb=AS:2000\r\na=rtpmap:96 H264/90000\r\na=fmtp:96 "
    "packetization-mode=1;profile-level-id=640015;sprop-parameter-sets="
    "Z2QAFazkBQfoQAAAAwBAAAAPA8WLRIA=,aOvssiw=\r\na=control:track1\r\nm="
    "application 0 RTP/AVP 97\r\nc=IN IP4 0.0.0.0\r\nb=AS:1000\r\na=rtpmap:97 "
    "vnd.onvif.metadata/10000\r\na=control:track2\r\n";

std::string const sample580 =
    "RTSP/1.0 200 OK\r\nCSeq: 1\r\nDate: Thu, Aug 11 2022 12:09:54 "
    "GMT\r\nContent-Base: rtsp://10.45.231.24:559/pint.sdp/\r\nContent-Type: "
    "application/sdp\r\nContent-Length: 580\r\n\r\nv=0\r\no=- 1660140466387175 "
    "1 IN IP4 10.1.23.23\r\ns=Session streamed by Pinkman\r\ni=pint.sdp\r\nt=0 "
    "0\r\na=tool:LIVE555 Streaming Media "
    "v2017.10.28\r\na=type:broadcast\r\na=control:*\r\na=range:npt=0-\r\na=x-"
    "qt-text-nam:Session streamed by "
    "Pinkman\r\na=x-qt-text-pint.sdp\r\nm=video 0 RTP/AVP 96\r\nc=IN IP4 "
    "0.0.0.0\r\nb=AS:2000\r\na=rtpmap:96 H264/90000\r\na=fmtp:96 "
    "packetization-mode=1;profile-level-id=640015;sprop-parameter-sets="
    "Z2QAFazkBQfoQAAAAwBAAAAPA8WLRIA=,aOvssiw=\r\na=control:track1\r\nm="
    "application 0 RTP/AVP 97\r\nc=IN IP4 0.0.0.0\r\nb=AS:1000\r\na=rtpmap:97 "
    "vnd.onvif.metadata/10000\r\na=control:track2\r\n";

印刷

Bytes Transferred: 166 (Success)
Describe Headers: RTSP/1.0 200 OK
CSeq: 1
Date: Thu, Aug 11 2022 12:09:54 GMT
Content-Base: rtsp://10.45.231.24:559/pint.sdp/
Content-Type: application/sdp
Content-Length: 580
Sdp Size: 580
Pre-buffer: 580
sdpinfo: v=0
o=- 1660140466387175 1 IN IP4 10.1.23.23
s=Session streamed by Pinkman
i=pint.sdp
t=0 0
a=tool:LIVE555 Streaming Media v2017.10.28
a=type:broadcast
a=control:*
a=range:npt=0-
a=x-qt-text-nam:Session streamed by Pinkman
a=x-qt-text-pint.sdp
m=video 0 RTP/AVP 96
c=IN IP4 0.0.0.0
b=AS:2000
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=640015;sprop-parameter-sets=Z2QAFazkBQfoQAAAAwBAAAAPA8WLRIA=,aOvssiw=
a=control:track1
m=application 0 RTP/AVP 97
c=IN IP4 0.0.0.0
b=AS:1000
a=rtpmap:97 vnd.onvif.metadata/10000
a=control:track2

修復超時

現在,如果您的服務器實際上發送了錯誤的 Content-Length 並且您希望maxResponseWaitingTime_ms有意義,則必須使用async_read接口並使用計時器來取消異步操作。 此時,我將使用 go 完整的野獸模式:

住在科利魯

#include <boost/algorithm/string/find.hpp>
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <fstream>
#include <iostream>

namespace net = boost::asio;

using boost::system::error_code;
using net::ip::tcp;
using net::buffers_begin;
namespace beast = boost::beast;
namespace http  = beast::http;

using namespace std::chrono_literals;

static std::string const m_rtspServerURI        = "/stream";
static auto const        maxResponseWaitingTime = 100ms;

extern std::string const sample590;
extern std::string const sample580;

bool foo() {
    net::thread_pool io(1);
    try {
        beast::tcp_stream s(io);
        s.expires_after(3s);
        s.async_connect({{}, 65200}, net::use_future)
            .get(); // TODO connect to your host

        {
            s.expires_after(1s);
            std::string const message =
                "DESCRIBE " + m_rtspServerURI + " RTSP/1.0\nCSeq: 1\r\n\r\n";

            net::async_write(s, net::buffer(message), net::use_future).get();
        }

        net::streambuf buf;
        s.expires_after(maxResponseWaitingTime);

        auto n =
            net::async_read_until(s, buf, "\r\n\r\n", net::use_future).get();
        std::cout << "Initial read " << n << std::endl;

        {
            // hack RTSP status line:
            auto firstbuf = *buf.data().begin();
            auto b = const_cast<char*>(net::buffer_cast<char const*>(firstbuf)),
                 e = b + std::min(n, firstbuf.size());
            auto i = boost::make_iterator_range(b, e);
            auto o = boost::algorithm::find_first(i, "RTSP/1.0");
            if (o.size() == 8)
                std::copy_n("HTTP/1.1", 8, o.begin());
        }

        http::response<http::string_body> res;
        http::async_read(s, buf, res, net::use_future).get();

        std::cout << "Describe headers: " << res.base() << std::endl;
        std::cout << "sdpInfo: " << res.body() << std::endl;

        // note buf may still contain data received *after* the body
        return true;
    } catch (boost::system::system_error const& se) {
        std::cout << "Error: " << se.code().message() << std::endl;
        return false;
    }
    io.join();
}

int main() {
    foo();
}

有一些更有趣的現場演示:

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM