字符串流与直接字符串
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=].
我正在尝试向服务器发送 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=].