我正在尝试接收以下格式的消息
<size> <data>
地点:
<size>
是一个整数,表示 <data>
<size>
和 <data>
由空格分隔。示例消息是:
19 {"command":"start"}
这是我必须解析的类:
class Command : {
public:
Command(boost::asio::ip::tcp::socket& socket) : _socket(socket) {}
std::future<boost::property_tree::ptree> Receive();
private:
void OnSize(const std::error_code&, size_t);
void OnData(const std::error_code&, size_t);
boost::asio::ip::tcp::socket& _socket;
boost::asio::streambuf _buffer;
std::promise<boost::property_tree::ptree> _promise;
}
首先我需要 . 我调用
async_read_until(...,' ',...)
将数据拉至(并包括)空白区域。 这一切都存储在 boost::asio::streambuf
:
std::future<boost::property_tree::ptree>
Command::Receive() {
boost::asio::async_read_until(_socket, _buffer, ' ',
std::bind(&Command::OnSize, this, _1, _2)
);
return _promise.get_future();
}
当这是阻塞时,我然后使用
netcat
发送示例消息:
$ netcat 127.0.0.1 14652
19 {"command":"start"}
如果我使用wireshark进行检查,整个消息将在单个TCP帧中发送。
处理程序
OnSize()
按预期调用,并使用 bytes_received = 3
。这是有道理的,因为 2 个字节表示 <size>
加上 1 个字节表示空白。 然后我希望我只需要 transfer_exactly(data_size)
阅读其余部分。
void Command::OnSize(const std::error_code& ec, std::size_t bytes_received) {
std::size_t data_size;
std::istream is(&_buffer);
is >> data_size; // automatically consumes the digits from the buffer
// consume the whitespace
_buffer.consume(1);
boost::asio::async_read(
_socket, _buffer, boost::asio::transfer_exactly(data_size),
std::bind(&Command::OnData, this, _1, _2)
);
}
接下来我打电话给
async_read(..., transfer_exactly(data_size), ...)
,希望 OnData()
用 bytes_received == data_size
打电话。 然而,我发现它会阻塞,直到我通过垃圾邮件发送随机数据来触发另一个至少 data_size
的 TCP 帧,这是一个问题,因为我不期望更多数据。
void Command::OnData(const std::error_code& ec, std::size_t bytes_received) {
std::istream is(&m_buffer);
boost::property_tree::ptree pt;
boost::property_tree::json_parser::read_json(is, pt);
_promise.set_value(pt);
}
有趣的是,
_buffer.size() == 23
位于OnSize()
的开头,而_buffer.size() == 20
位于async_read
之前。 这意味着其余数据实际上已经写入缓冲区。 我该怎么办?
这是一个解决方案,但我不知道这是否是正确的方法。 这个想法是检查缓冲区中有多少数据,并且仅
asyc_read()
缓冲区中已有的数据与您期望的数据之间的差异。
void Command::OnSize(const std::error_code& ec, std::size_t bytes_received) {
std::size_t data_size;
std::istream is(&_buffer);
is >> data_size;
_buffer.consume(1);
auto unread_bytes = _buffer.size();
if (unread_bytes >= data_size) {
OnData(ec, data_size);
}
else {
auto remaining = data_size - unread_bytes;
boost::asio::async_read(
_socket, _buffer, boost::asio::transfer_exactly(remaining),
std::bind(&Command::OnData, this, _1, _2)
);
}
}
我无法用大数据集对此进行测试,但
unread_bytes >= data_size
条件似乎效果很好。 在我的lo
设备上,即使对于大型数据集(5000字节),这似乎也有效,尽管我通过wireshark确认这仍然是在单个TCP帧上发送的。