简体   繁体   中英

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

I am developing a RTSP client. Its a simple text based protocol. The server sends me a response which i can see on Wireshark, but I don't get all of it when I try to read it.

THe data sent is like this (from 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

My strategy to read it was to first "read_until" the content length, then use the content length to read the rest of the data.

Instead, I get only about half the data when I try it. The second read just hangs. I thought it had something to do with the size of the socket buffer, but that was not it.

Here is the code:

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

WHat am I doing wrong?

The content length is wrong. It is 580 bytes. You will be waiting for 10 more bytes indefinitely.

Besides, when using transfer_exactly the bytes already buffered are not counted. This is unfortunate:

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

Even this extremely minimized test program will hang because transfer_exactly(1) does not take into account the 4 remaining bytes already in the buffer.

Instead you should either make a functional completion condition that takes existing buffer contents into account, OR do the math manually:

Live On Coliru

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

Now it correctly prints:

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

Applying

I'd personally favor using existing parsing libraries. Sadly, RTSP is not standard HTTP, so I hacked up a utility function based on Beast:

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

Here we pacify the HTTP parser by replacing the RTSP status line with a dummy HTTP status line. Then we can use the existing header-parsing and validation to extract the content-length header field value.

Now, we can implement your function working:

Coliru

#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";

Prints

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

Fixing Timeout

Now, if your server actually sends the wrong Content-Length and you wanted maxResponseWaitingTime_ms to be meaningful, you'd have to use the async_read interface and use a timer to cancel the async operation. At this point, I'd go full Beast mode instead:

Live On Coliru

#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();
}

With some more interesting live demo:

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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