字符串流与直接字符串

string stream vs direct string

我正在尝试向服务器发送 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[=10=]"};

    // 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.
       << '[=10=]'; // 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));
}

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[=10=]"};
    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('[=10=]');
    std::cout << message2.length() << std::endl;
}

输出:

97
97
98

因此您只需要创建没有终止 NULL 字符的消息,然后附加 [=16=].

更新: 如果您在 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[=12=]"s;

注意字符串末尾的 s

您还可以使用带有迭代器的字符串构造函数来复制所有字符,包括尾随 \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;
}
const std::string message {"STOMP\naccept-version:1.2\nhost:transportforlondon.com\nlogin:fake_username\npasscode:fake_password\n\n[=10=]"};

这会失败,因为您正在使用 std::string 构造函数构造 message,该构造函数仅接受 null-terminated const char* 指针,并且它将通过查找终止 '[=16=]' 来计算字符串长度。由于您的字符串文字中包含一个明确的 '[=16=]' ,因此构造函数会在此处停止读取字符。 '[=16=]' 本身未被复制。

要解决此问题,您需要指定字符串文字的长度 包括 显式 '[=16=]',例如:

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

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

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

std::stringstream 方法起作用的原因是因为 << '[=21=]' 将实际的 '[=16=]' 字符插入到流中,然后包含在 std::string 返回的 std::string 中=24=].