通过网络构建和发送二进制数据
我正在为我的世界创建一个命令行客户端.可以在此处找到有关该协议的完整规范:http://mc.kev009.com/Protocol一个>.事先回答你的问题,是的,我有点 C++ 菜鸟.
I am creating a command-line client for minecraft. There is a full spec on the protocol that can be found here: http://mc.kev009.com/Protocol. To answer your question beforehand, yes I am a bit of a C++ noob.
我在实现此协议时遇到了各种问题,其中每个问题都很关键.
I have various issues in implementing this protocol, of which each critical.
- 协议规定所有类型都是大端的.我不知道应该如何检查我的数据是否为小端,如果是,如何转换为大端.
- 字符串数据类型有点奇怪.它是一个修改后的 UTF-8 字符串,前面有一个包含字符串长度的短字符串.我不知道应该如何将它打包成一个简单的 char[] 数组,也不知道如何将我的简单字符串转换为修改后的 UTF-8 字符串.
- 即使我知道如何将数据转换为大端并创建修改后的 UTF-8 字符串,我仍然不知道如何将其打包到 char[] 数组中并将其作为包发送.我之前所做的只是简单的 ASCII 格式的 HTTP 网络.
非常感谢解释、链接、相关函数名称和简短片段!
Explanations, links, related function names and short snippets much appreciated!
编辑
现在回答 1 和 3.1 由 user470379 在下面回答.3 由这个 AWESOME 线程回答,它解释了我想做的很好:http://cboard.cprogramming.com/networking-device-communication/68196-sending-non-char*-data.html 我不确定修改后的 UTF-不过还有 8 个.
1 and 3 is answered now. 1 is answered below by user470379. 3 is answered by this AWESOME thread that explains what I want to do very well: http://cboard.cprogramming.com/networking-device-communication/68196-sending-non-char*-data.html I'm not sure about the modified UTF-8 yet though.
推荐答案
传统的做法是为每个协议消息定义一个C++消息结构,并为其实现序列化和反序列化功能.例如 登录请求 可以这样表示:
A traditional approach is to define a C++ message structure for each protocol message and implement serialization and deserialization functions for it. For example Login Request can be represented like this:
#include <string>
#include <stdint.h>
struct LoginRequest
{
int32_t protocol_version;
std::string username;
std::string password;
int64_t map_seed;
int8_t dimension;
};
现在需要序列化函数.首先它需要整数和字符串的序列化函数,因为这些是 LoginRequest
中的成员类型.
Now serialization functions are required. First it needs serialization functions for integers and strings, since these are the types of members in LoginRequest
.
整数序列化函数需要与大端表示进行转换.由于消息的成员被复制到缓冲区和从缓冲区复制,因此可以在复制时反转字节顺序:
Integer serialization functions need to do conversions to and from big-endian representation. Since members of the message are copied to and from the buffer, the reversal of the byte order can be done while copying:
#include <boost/detail/endian.hpp>
#include <algorithm>
#ifdef BOOST_LITTLE_ENDIAN
inline void xcopy(void* dst, void const* src, size_t n)
{
char const* csrc = static_cast<char const*>(src);
std::reverse_copy(csrc, csrc + n, static_cast<char*>(dst));
}
#elif defined(BOOST_BIG_ENDIAN)
inline void xcopy(void* dst, void const* src, size_t n)
{
char const* csrc = static_cast<char const*>(src);
std::copy(csrc, csrc + n, static_cast<char*>(dst));
}
#endif
// serialize an integer in big-endian format
// returns one past the last written byte, or >buf_end if would overflow
template<class T>
typename boost::enable_if<boost::is_integral<T>, char*>::type serialize(T val, char* buf_beg, char* buf_end)
{
char* p = buf_beg + sizeof(T);
if(p <= buf_end)
xcopy(buf_beg, &val, sizeof(T));
return p;
}
// deserialize an integer from big-endian format
// returns one past the last written byte, or >buf_end if would underflow (incomplete message)
template<class T>
typename boost::enable_if<boost::is_integral<T>, char const*>::type deserialize(T& val, char const* buf_beg, char const* buf_end)
{
char const* p = buf_beg + sizeof(T);
if(p <= buf_end)
xcopy(&val, buf_beg, sizeof(T));
return p;
}
对于字符串(处理修改后的 UTF-8 的方式与 asciiz 字符串相同):
// serialize a UTF-8 string
// returns one past the last written byte, or >buf_end if would overflow
char* serialize(std::string const& val, char* buf_beg, char* buf_end)
{
int16_t len = val.size();
buf_beg = serialize(len, buf_beg, buf_end);
char* p = buf_beg + len;
if(p <= buf_end)
memcpy(buf_beg, val.data(), len);
return p;
}
// deserialize a UTF-8 string
// returns one past the last written byte, or >buf_end if would underflow (incomplete message)
char const* deserialize(std::string& val, char const* buf_beg, char const* buf_end)
{
int16_t len;
buf_beg = deserialize(len, buf_beg, buf_end);
if(buf_beg > buf_end)
return buf_beg; // incomplete message
char const* p = buf_beg + len;
if(p <= buf_end)
val.assign(buf_beg, p);
return p;
}
还有几个辅助函子:
struct Serializer
{
template<class T>
char* operator()(T const& val, char* buf_beg, char* buf_end)
{
return serialize(val, buf_beg, buf_end);
}
};
struct Deserializer
{
template<class T>
char const* operator()(T& val, char const* buf_beg, char const* buf_end)
{
return deserialize(val, buf_beg, buf_end);
}
};
现在使用这些原始函数,我们可以轻松地序列化和反序列化 LoginRequest
消息:
Now using these primitive functions we can readily serialize and deserialize LoginRequest
message:
template<class Iterator, class Functor>
Iterator do_io(LoginRequest& msg, Iterator buf_beg, Iterator buf_end, Functor f)
{
buf_beg = f(msg.protocol_version, buf_beg, buf_end);
buf_beg = f(msg.username, buf_beg, buf_end);
buf_beg = f(msg.password, buf_beg, buf_end);
buf_beg = f(msg.map_seed, buf_beg, buf_end);
buf_beg = f(msg.dimension, buf_beg, buf_end);
return buf_beg;
}
char* serialize(LoginRequest const& msg, char* buf_beg, char* buf_end)
{
return do_io(const_cast<LoginRequest&>(msg), buf_beg, buf_end, Serializer());
}
char const* deserialize(LoginRequest& msg, char const* buf_beg, char const* buf_end)
{
return do_io(msg, buf_beg, buf_end, Deserializer());
}
使用上面的辅助函子并将输入/输出缓冲区表示为 char
迭代器范围,只需要一个函数模板来执行消息的序列化和反序列化.
Using the helper functors above and representing input/output buffers as char
iterator ranges only one function template is required to do both serialization and deserialization of the message.
综合起来,用法:
int main()
{
char buf[0x100];
char* buf_beg = buf;
char* buf_end = buf + sizeof buf;
LoginRequest msg;
char* msg_end_1 = serialize(msg, buf, buf_end);
if(msg_end_1 > buf_end)
; // more buffer space required to serialize the message
char const* msg_end_2 = deserialize(msg, buf_beg, buf_end);
if(msg_end_2 > buf_end)
; // incomplete message, more data required
}
相关文章