You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Boost.Asio读取RTSP响应数据不全,如何排查问题?

RTSP客户端读取数据不完整问题排查

我正在开发一款RTSP客户端,这是基于文本的协议。通过Wireshark能看到服务器发送的完整响应,但用Boost.Asio读取时无法获取全部数据。我的读取策略是先用read_until读取到Content-Length,再根据该长度读取剩余数据,但实际仅能获取约一半数据,第二次read操作直接挂起。曾怀疑是套接字缓冲区大小问题,但排除了这个可能。

服务器响应数据(来自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

相关代码

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

错误点分析及修复

1. 请求格式不符合RTSP协议规范

RTSP协议要求所有行的结束符必须是\r\n,但你发送的DESCRIBE请求第一行结尾用的是\n:

std::string message = "DESCRIBE " + m_rtspServerURI + " RTSP/1.0\nCSeq: 1\r\n\r\n";

把第一行的\n改成\r\n,修正后的请求:

std::string message = "DESCRIBE " + m_rtspServerURI + " RTSP/1.0\r\nCSeq: 1\r\n\r\n";

格式错误可能导致服务器解析请求异常,进而影响响应的发送逻辑。

2. 不必要的sleep导致时序问题

std::this_thread::sleep_for完全多余,Boost.Asio的同步读取本身会阻塞直到数据到达。sleep可能导致服务器已发送的数据在缓冲区堆积,后续读取逻辑出现混乱,直接删除这行代码即可。

3. 错误的缓冲区消费逻辑(核心问题)

TCP是流式传输,服务器可能一次性把RTSP头部和全部SDP数据发送过来。当你用read_until读取到\r\n\r\n时,receive_buffer中可能已经包含了部分甚至全部SDP数据,但你直接调用receive_buffer.consume(bytes_transferred)将这些SDP数据一并丢弃,之后再尝试读取sdpSize长度的数据,自然会挂起——因为需要的部分数据已经被你扔掉,服务器已经发完所有数据了。

修复后的缓冲区处理逻辑:

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

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

std::size_t sdpSize = extractContentLength(describeHeaders);
std::cout << "Describe Headers:: \n" << describeHeaders << "\n\n" << "Sdp Size: " << sdpSize << "\n";

std::string sdpInfo;
std::size_t remaining_sdp = sdpSize;

// 先处理缓冲区中已有的SDP数据(分隔符之后的部分)
std::size_t available_after_delimiter = receive_buffer.size() - bytes_transferred;
if (available_after_delimiter > 0)
{
    std::size_t take = std::min(available_after_delimiter, remaining_sdp);
    sdpInfo.append(
        boost::asio::buffers_begin(receive_buffer.data()) + bytes_transferred,
        boost::asio::buffers_begin(receive_buffer.data()) + bytes_transferred + take
    );
    remaining_sdp -= take;
}

// 消费已经处理的部分(头部+分隔符+已读取的SDP)
receive_buffer.consume(bytes_transferred + (sdpSize - remaining_sdp));

// 如果还有剩余数据需要读取,再从socket读取
if (remaining_sdp > 0)
{
    boost::asio::read(*m_socket, receive_buffer, boost::asio::transfer_exactly(remaining_sdp), error);
    if (error)
    {
        std::cout << "read SDP failed: " << error.message() << std::endl;
        return false;
    }
    sdpInfo.append(
        boost::asio::buffers_begin(receive_buffer.data()),
        boost::asio::buffers_begin(receive_buffer.data()) + remaining_sdp
    );
    receive_buffer.consume(remaining_sdp);
}

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

4. 验证extractContentLength函数的正确性

确保extractContentLength能正确解析Content-Length字段:

  • 忽略字段名的大小写(比如服务器返回content-length也要能识别)
  • 正确提取字段值中的数字,排除前后的空格
  • 处理字段不存在的异常情况(虽然RTSP响应通常会包含,但需要容错)

内容的提问来源于stack exchange,提问作者Chani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 05:45:31