使用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
相关产品推荐
相关产品推荐

