C++ Boost ASIO-TCP数据包的标头和正文无效
我正在使用Boost ASIO作为我的项目的TCP网络通信解决方案 这是我的密码:C++ Boost ASIO-TCP数据包的标头和正文无效,c++,sockets,network-programming,boost-asio,C++,Sockets,Network Programming,Boost Asio,我正在使用Boost ASIO作为我的项目的TCP网络通信解决方案 这是我的密码: Client.h: namespace Vibranium{ class Client: public std::enable_shared_from_this<Client> { public: Client(tcp::socket socket) : socket(std::move(socket)) { }
Client.h
:
namespace Vibranium{
class Client: public std::enable_shared_from_this<Client>
{
public:
Client(tcp::socket socket)
: socket(std::move(socket))
{
}
void start();
int connectionId;
tcp::socket socket;
void Send(ServerOpcode serverOpcode, const std::string& message);
private:
void read_header();
void read_body();
Packet _packet;
};
}
#endif //VIBRANIUM_CORE_CLIENT_H
为了避免这个问题被代码淹没,这里是Packet.h
:和Packet.cpp
:的内容:
我所能看到的是,不知何故,头和体都被传输了,但它们没有正确解析。为什么我在服务器上收到如此未格式化的输出?我的错误在哪里?如何纠正?分析
让我们一步一步地通过代码来了解发生了什么。在您的客户机中,您正在初始化大小为8+1024
的缓冲区,您可以将一些数据填入其中:
char data_[header_length + body_length];
...
sprintf(data_, "%s %s", header_, body_);
您可以看到写入数据的数据小于整个缓冲区的大小。由于您没有对数据进行零初始化
,因此它的剩余部分将填充随机数据(之前堆栈上该位置发生的任何情况)。字符串“%s%s”
是一个以零结尾的字符串,这意味着在内存中它后面将紧跟一个结束的零字节。因此,当sprintf
将标题和正文插入此格式化字符串时,您将再次获得一个以零结尾的字符串,该字符串将写入数据。因此,数据
缓冲区将包含:
[--- header ---] <space> [--- body---] <zero-byte> [--- random data ---]
因此,客户机中的打印不会显示缓冲区末尾的随机数据
然后将整个缓冲区发送到服务器:
New Connection (ID: 1)
Header:
1
I am
Body:
testing here!!!
hP��U��TK���U�U�� K�h
�*�� K�) �J�Uh
0�y���x������������RK�Px���� K��RK�`x��\TUK��TK��;�#K��TJ�d�"K��XUK�v\TUK� �TK�{�|@X#K� �TJ�`FK��XUK��\TUK� �TK��G�|X^#K� �TJ�4AK��XUK�:\TUK��TK�z���M$K��TJ���#K��XUK��
u��u�z��x�TK��u��<}(K�@u��Pu���aUK��TJ��TK������TJ��TK�x�TK���U�{��������UK��M$K��TK����@K��{����UK�
Invalid header sent!
size_t request_length = sizeof(data_)/sizeof(*data_);
boost::asio::write(s, boost::asio::buffer(data_, request_length));
在服务器中,您读取的缓冲区大小等于标头的大小:
boost::asio::async_read(socket,boost::asio::buffer(_packet.data_, _packet.header_length), ...);
因此,这将处理您在客户机中写入的缓冲区的第一个头\u长度
字节。如上所述,缓冲区的第一个头\u长度
字节可以包含分隔空间和正文的某些部分,具体取决于您在客户端中写入缓冲区的头数据的长度。因此,当您使用此行在服务器中打印标题时,您将看到部分正文数据:
std::cout.write(_packet.data_, _packet.header_length);
然后通过读取body\u length
多个字节继续读取正文。这将是您在客户端中写入的剩余字节。因此,它将包含主体的剩余部分(减去已作为头的一部分处理的部分)、零字节,然后是随机数据
使用此行打印缓冲区,该行不会在零字节处停止:
std::cout.write(_packet.data_, _packet.body_length);
因此,您将看到身体的截断部分,然后在日志中看到一些随机数据
如何解决这个问题?
首先,当您创建缓冲区时,您应该通过零初始化来确保它不包含随机数据。否则,在通过网络发送缓冲区时,您有可能会泄漏敏感数据(加密密钥、密码)。考虑下面的示例如何初始化零:
std::cout << "Printing test" << std::endl;
// Not zero-initialized, will print garbage.
char test[10];
std::cout.write(test, sizeof(test));
std::cout << std::endl;
std::cout << "Printing test2" << std::endl;
// Zero-initialized, will print nothing (because \0 is not printable)
char test2[10]{};
std::cout.write(test2, sizeof(test2));
std::cout << std::endl;
您可以使用以下方法检查缓冲区内容:
// Debug: Print buffer content, requires #include <iomanip>
for (char c : buffer) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c) << ' ';
}
std::cout << std::endl;
您可以看到没有随机数据出现,因为缓冲区已初始化为零。页眉数据的大小仅为1字节,但页眉数据的结尾和正文数据之间存在填充字节,从页眉长度开始。
字节。精彩的解释!非常感谢你,先生!在我提出这个问题之后,我碰巧想出了一个解决办法。我已将header\uu
从char更改为std::string header\uu代码>比写入流之前我做头=std::to_字符串(opc)代码>和标题。调整大小(标题长度)代码>然后在阅读侧我再次调整大小。在处理TCP数据包时,使用char
数组而不是string
和resize是否有任何显著的好处?两点:从编码风格的角度来看,我更喜欢std::array
变量,因为它(即,您希望处理二进制blob而不是文本)。从性能角度来看,std::array
变体的性能会稍好一些。我做了一些简单的测量,两个变量重复了10亿次,用-O3
编译,得到了std::to_string
+复制到std::array
变量与std::to_string
+调整大小的16s。您可能关心也可能不关心这样的差异。我不确定如何将标题和长度的std::array
s组合在一起,以及如何在另一侧读取它们。你能举一个例子,说明我如何用接收部分的字符数组读取数据吗?还有动态体大小。一个信息是身体大小不同,另一个则不同。我正计划创建一个序列1.Header 2.BodySize 3.Body
。在这种情况下,主体尺寸将设置在第二个平面上,因此,当读取部分看到标题时,它将在标题之后准备主体尺寸,然后知道预期的主体有多大,将准确读取那么多。然而,这将迫使我调整缓冲区{}
。我怎样才能做到这一点?这是一个好主意吗?使用你的方法,我进入了另一个问题:你能检查一下吗?
std::cout.write(_packet.data_, _packet.header_length);
std::cout.write(_packet.data_, _packet.body_length);
std::cout << "Printing test" << std::endl;
// Not zero-initialized, will print garbage.
char test[10];
std::cout.write(test, sizeof(test));
std::cout << std::endl;
std::cout << "Printing test2" << std::endl;
// Zero-initialized, will print nothing (because \0 is not printable)
char test2[10]{};
std::cout.write(test2, sizeof(test2));
std::cout << std::endl;
int op_code = 1;
std::string header = std::to_string(op_code);
constexpr size_t header_length = 8;
std::string body{"abc"};
constexpr size_t body_length = 32;
// Zero-initialize buffer, requires #include <array>
std::array<char, header_length + body_length> buffer{};
// TODO Check header.size() <= header_length
// TODO Check body.size() <= body_length
std::copy(header.begin(), header.end(), buffer.begin());
std::copy(body.begin(), body.end(), buffer.begin() + header_length);
// Debug: Print buffer content, requires #include <iomanip>
for (char c : buffer) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c) << ' ';
}
std::cout << std::endl;
31 00 00 00 00 00 00 00 61 62 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00