【问题标题】:string stream vs direct string字符串流与直接字符串
【发布时间】:2021-11-20 14:14:42
【问题描述】:

我正在尝试向服务器发送一个 STOMP 帧。我首先尝试使用 std::string 直接创建消息,但服务器一直抱怨我做错了。但是,当我使用 stringstream 创建消息时,它可以工作。任何人都可以发现我的错误吗?代码如图所示。它抱怨它在消息末尾找不到终止的 \0 (parsingmissingnullinbody)。

bool CheckResponse(const std::string& response)
{
// We do not parse the whole message. We only check that it contains some
// expected items.
bool ok {true};
ok &= response.find("ERROR") != std::string::npos;
ok &= response.find("ValidationInvalidAuth") != std::string::npos;
std::cout << response << "\n";
return ok;
}

BOOST_AUTO_TEST_CASE(connect_to_network_events){
    // Always start with an I/O context object.
    boost::asio::io_context ioc {};

    // Create also a tls context
    boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv12_client};
    // Connection targets
    const std::string url {"ltnm.learncppthroughprojects.com"};
    const std::string port {"443"};
    const std::string endpoint {"/network-events"};

    // The class under test
    WebSocketClient client {url, endpoint, port, ioc, ctx};

    // MY ATTEMPT AT CREATING MESSAGE DIRECTLY, THIS FAILED
    // const std::string message {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"};

    // THE FOLLOWING SUCCEEDED INSTEAD
    const std::string username {"fake_username"};
    const std::string password {"fake_password"};
    std::stringstream ss {};
    ss << "STOMP" << std::endl
       << "accept-version:1.2" << std::endl
       << "host:transportforlondon.com" << std::endl
       << "login:" << username << std::endl
       << "passcode:" << password << std::endl
       << std::endl // Headers need to be followed by a blank line.
       << '\0'; // The body (even if absent) must be followed by a NULL octet.
    const std::string message {ss.str()};

    std::string response;
    // We use these flags to check that the connection, send, receive functions
    // work as expected.
    bool connected {false};
    bool messageSent {false};
    bool messageReceived {false};
    bool messageMatches {false};
    bool disconnected {false};

    // Our own callbacks
    auto onSend {[&messageSent](auto ec) {
        messageSent = !ec;
    }};
    auto onConnect {[&client, &connected, &onSend, &message](auto ec) {
        connected = !ec;
        if (!ec) {
            client.Send(message, onSend);
        }
    }};
    auto onClose {[&disconnected](auto ec) {
        disconnected = !ec;
    }};
    auto onReceive {[&client,
                      &onClose,
                      &messageReceived,
                      &messageMatches,
                      &message,
                      &response](auto ec, auto received) {
        messageReceived = !ec;
        response = received;
        client.Close(onClose);
    }};

    // We must call io_context::run for asynchronous callbacks to run.
    client.Connect(onConnect, onReceive);
    ioc.run();

    BOOST_CHECK(CheckResponse(response));
}

【问题讨论】:

  • 发送消息处理器正在寻找的实际二进制终止符,而不是std::endl

标签: c++ stomp


【解决方案1】:

const char * 创建std::string 将忽略终止NULL 字符。您可以使用 char[] 构造函数,但我认为它不适合您的用例。

例子:

// Example program
#include <iostream>
#include <string>

int main()
{
    std::string message1 {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"};
    std::cout << message1.length() << std::endl;
    
    std::string message2 {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n"};
    std::cout << message2.length() << std::endl;
    
    message2.push_back('\0');
    std::cout << message2.length() << std::endl;
}

输出:

97
97
98

因此,您只需要创建没有终止 NULL 字符的消息,然后附加 \0

更新:如果你在C++14,你可能想要使用字符串文字:

using namespace std::string_literals;

// ... 

std::string message = "STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"s;

注意字符串末尾的s

【讨论】:

  • 有效!那么从std::string创建字符串时,\0不被视为文字,所以我们需要手动追加它?
  • std::string 的构造函数之一采用 const char *,也就是 C 字符串。 C 字符串以 NULL 结尾。即:NULL 标记字符串的结尾,但它不是它的一部分。
【解决方案2】:

您还可以使用带有迭代器的字符串构造函数来复制所有字符,包括尾随 \0

#include <cassert>
#include <string>

// helper function to copy ALL the chars including terminating 0 into a string
template<std::size_t N>
auto create_message(const char(&chars)[N])
{
    std::string message(std::begin(chars), std::end(chars));
    return message;
}

int main()
{
    auto message = create_message("STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n");

    // check last character in string is actually 0
    assert(message[message.length() - 1] == 0);
    return 0;
}

【讨论】:

    【解决方案3】:
    const std::string message {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"};
    

    这失败了,因为您正在使用 std::string 构造函数构造 message,该构造函数仅接受 null-terminated const char* 指针,它将通过查找终止 @ 来计算字符串长度987654325@。由于您的字符串文字中包含显式 '\0' ,因此构造函数停止读取字符。 '\0' 本身不会被复制。

    要解决这个问题,您需要指定字符串文字的长度包括显式'\0',例如:

    const std::string message ("STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0", 98);
    

    或者,让编译器为您计算长度:

    const char msg[] = "STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n\0"; // will include an implicit '\0' after the explicit '\0'...
    const std::string message (msg, std::size(msg)-1); // -1 to ignore the implicit '\0'...
    

    std::stringstream 方法起作用的原因是因为&lt;&lt; '\0' 将实际的'\0' 字符插入到流中,然后将其包含在str() 返回的std::string 中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-09-07
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-27
      相关资源
      最近更新 更多