提问人:malloy 提问时间:6/14/2023 更新时间:6/14/2023 访问量:126
如何在 C++ 中从流中提取信息
How to extract information from a stream in C++
问:
我有一个远程服务器,它不断向我的电脑发送这样的消息:
{Heartbeat}
此外,远程服务器会使用 json 格式的字符串侦听我从连接到远程服务器的 PC 发送的命令消息,例如:
{'H': '1', 'N': 3, 'D1': 3, 'D2': 150}
然后,远程服务器向我发送响应,例如:
{1:ok}
然而,有时,答案会被各种心跳淹没,例如:
{心跳}{心跳}{心跳}{1:确定}{心跳}{心跳}
如何使用 C++17 过滤这个长序列 {...},仅提取有用的信息 {1:ok}? 作为 TCP/IP 通信库,我使用 boost ASIO 1.81.0。
对于 C++17 中的 TCP/IP 通信,我使用了 TCPClient 类。我主要向服务器发送以下命令:
client->Send_cmd("{\'H\': \'1\', \'N\': 3, \'D1\': 3, \'D2\': 150}");
如 TCPClient 类所示,我用作向服务器发送消息的成员函数,如下所示:
void TCPClient::Send_cmd(std::string data)
{
// search for substring
std::string ricerca = "";
std::string response = "";
std::string response_tmp = "";
std::string::iterator i1; // Declaration of an iterator to store the returned pointer
boost::system::error_code error;
// result indicates the number of data bytes transmitted
auto result = boost::asio::write(*m_socket, boost::asio::buffer(data), error);
if (!error) {
esito_cmd_al_server = "Client sent the command";
}
else {
esito_cmd_al_server = "send failed: " + error.message();
}
// We just have to read the response from the server
for (;;)
{
// We use a boost::array to hold the received data.
// Instead of a boost::array, we could have used a char [] or std::vector.
//boost::array<char, 128> buf;
boost::asio::streambuf buf2;
boost::system::error_code error;
// The boost::asio::buffer() function automatically determines the size
// of the array to help prevent buffer overflow.
//size_t len = m_socket->read_some(boost::asio::buffer(buf), error);
size_t len2 = boost::asio::read_until(*m_socket, buf2, "}");
// for correctness I keep the response on the first {...} and if there are other {...} I delete them
ricerca = "}";
response = boost::asio::buffer_cast<const char*>(buf2.data());
// look for the first occurrence of "}"
i1 = std::search(response.begin(), response.end(), ricerca.begin(), ricerca.end());
// if it encountered "}", then delete the rest of the response
if (i1 != response.end())
{
std::string finale2 = response.substr(0, (i1 - response.begin()) + 1);
response = finale2;
}
// check that the incoming message from the server-robot is not a "{Heartbeat}"
std::string search = "{Hea";
i1 = std::search(response.begin(), response.end(), search.begin(), search.end());
// if it encountered "{Heartbeat}", then repeat the for loop
if (i1 != response.end())
{
response = "";
continue;
}
response_tmp = response;
// When the server closes the connection, the function boost::asio::ip::tcp::socket::read_some()
// will exit with error boost::asio::error::eof
if (error == boost::asio::error::eof)
break; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error(error); // Some other error.
//std::string tmp_str(buf.data(), len);
//std::string tmp_str2 = boost::asio::buffer_cast<const char*>(buf2.data());
//esito_cmd_al_server = tmp_str2;
esito_cmd_al_server = response_tmp;
if ("{Heartbeat}" == esito_cmd_al_server)
continue;
else
break;
}
}
有没有更正确的方法(我的似乎有点麻烦)来执行我写的过滤?
答:
我会使用该界面,因为您已经在使用 .istream
streambuf
请注意,您需要将缓冲区保留为成员变量,因为可能会超出分隔符的读取范围,并且您通常不希望丢失该数据。read_until
另一方面,我不会将响应/结果存储在成员中,而是返回它们(通过返回值或异常,因为您已经部分实现了):
read_until(*m_socket, m_readbuf, "}");
std::string msg;
getline(std::istream(&m_readbuf), msg, '}'); // excl '}'
if (msg == "{Heartbeat")
continue;
return msg;
如果你坚持,你可以手动进行解析,但真正考虑使用字符串视图来提高效率和优雅(例如,“每个人都讨厌字符串/string_view接口,但如果你找到最佳位置,它就会非常强大。
当然,由于它看起来几乎是 JSON,因此可以考虑将其与 boost::json::stream_parser
一起使用
现场演示
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
struct TCPClient {
std::string Send_cmd(std::string const&);
// private:
asio::io_context m_io;
std::unique_ptr<tcp::socket> m_socket{new tcp::socket(m_io)};
asio::streambuf m_readbuf;
};
std::string TCPClient::Send_cmd(std::string const& data) try {
write(*m_socket, asio::buffer(data));
for (;;) { // wait for response
read_until(*m_socket, m_readbuf, "}");
std::string msg;
getline(std::istream(&m_readbuf), msg, '}');
msg += '}'; // getline excludes delimiter
std::cerr << "Debug: " << quoted(msg) << std::endl;
if (msg != "{Heartbeat}")
return msg;
}
} catch (boost::system::system_error const& se) {
if (se.code() != asio::error::eof)
throw;
return ""; // closed by peer
}
int main() {
TCPClient demo;
demo.m_socket->connect({{}, 7878});
std::string response = demo.Send_cmd("{'H': '1', 'N': 3, 'D1': 3, 'D2': 150}");
std::cout << "Response: " << quoted(response) << std::endl;
}
使用假服务器进行测试,例如
(for a in {1..3}; do sleep 2; echo -n '{Heartbeat}'; done; echo -n "{'42': 'ok'}") | netcat -lp 7878
奖励:异步
检测信号协议意味着异步/全双工 IO。您正在使用同步 IO,这会产生“问题”。请考虑使用单独的读取循环和发送循环以及匹配请求响应的代码。使用该协议的示例如下:使用 Boost Beast 的 Chrome DevTools 协议
评论