如何在 C++ 中从流中提取信息

How to extract information from a stream in C++

提问人:malloy 提问时间:6/14/2023 更新时间:6/14/2023 访问量:126

问:

我有一个远程服务器,它不断向我的电脑发送这样的消息:

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

有没有更正确的方法(我的似乎有点麻烦)来执行我写的过滤?

C++17 升压-ASIO 标准字符串

评论


答:

0赞 sehe 6/14/2023 #1

我会使用该界面,因为您已经在使用 .istreamstreambuf

请注意,您需要将缓冲区保留为成员变量,因为可能会超出分隔符的读取范围,并且您通常不希望丢失该数据。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 一起使用

现场演示

Live On Coliru(科里鲁生活公寓)

#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

enter image description here

奖励:异步

检测信号协议意味着异步/全双工 IO。您正在使用同步 IO,这会产生“问题”。请考虑使用单独的读取循环和发送循环以及匹配请求响应的代码。使用该协议的示例如下:使用 Boost Beast 的 Chrome DevTools 协议

评论

0赞 malloy 6/15/2023
谢谢你的解释,sehe。我有兴趣能够将 TCPClient 类编写为异步。你能告诉我在哪里可以找到使用 boost asio 的异步客户端示例吗?我找不到,它们总是同步的。
0赞 sehe 6/15/2023
大多数是异步的,因为这是 Asio 库的目的(它的名字就在名中!所有示例在相关的情况下都具有两种风格。请注意,一些高级示例仅是异步的。此外,我的答案链接到一个完整的例子。
0赞 sehe 6/15/2023
你也可以做的是描述任务/显示当前代码,我可以帮助你开始。您也可以这样做,如果您有任何问题,请发布一个新问题
1赞 malloy 6/16/2023
我听从你的建议,sehe。我去boost站点并举例说明客户端和服务器:-example_cpp03_echo_async_tcp_echo_server.cpp - 1.81.0 -example_cpp03_echo_blocking_tcp_echo_client.cpp - 1.81.0 -example_cpp03_timeouts_async_tcp_client.cpp - 1.81.0 看看应该如何设置,然后我尝试编写一个客户端发送消息,服务器通过发送另一条消息进行回复。