通过网络构建和发送二进制数据
-
24-10-2019 - |
题
我正在为Minecraft创建一个命令行客户端。有关协议的完整规格,可以在此处找到: http://mc.kev009.com/protocol. 。要事先回答您的问题,是的,我有点像C ++菜鸟。
我在实施此协议方面有各种问题,每个协议都至关重要。
- 该协议说,所有类型都是大型的。我不知道我应该如何检查我的数据是否是小代数,如果是的话,如何转换为大型。
- 字符串数据类型有点奇怪。这是一个修改后的UTF-8字符串,之前是包含字符串长度的简短。我不知道如何将其包装到一个简单的char []数组中,也不知道如何将简单的字符串转换为修改后的UTF-8。
- 即使我知道如何将我的数据转换为大端并创建修改后的UTF-8字符串,我仍然不知道如何将其包装到char []阵列中并作为包装发送。我之前所做的只是简单的HTTP网络,即普通的ASCII。
解释,链接,相关功能名称和简短片段非常感谢!
编辑
现在回答1和3。 1由User470379回答1。这个很棒的线程回答了3,它解释了我想做的事情: http://cboard.cprogramming.com/networking-device-communication/68196-sending-non-charda-data.html 我不确定修改后的UTF-8。
解决方案
一种传统的方法是为每个协议消息定义C ++消息结构,并为其实施序列化和避免函数。例如 登录请求 可以这样表示:
#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
.
整数序列化函数需要对大端表示和从大型表示进行转换。由于消息的成员被复制到缓冲区和从缓冲区中复制,因此可以在复制时完成字节顺序的逆转:
#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
信息:
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
迭代器范围仅需要一个函数模板才能同时进行消息的序列化和避难所化。
并将所有用法放在一起:
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
}
其他提示
对于#1,您需要使用 ntohs
和朋友。使用 *s
(简短)16位整数的版本, *l
(长)32位整数的版本。这 hton*
(主机到网络)将独立于您所使用的平台的端度和 ntoh*
(网络为主机)将转换传入的数据(再次,独立于平台endianness)
从我的头顶上...
const char* s; // the string you want to send
short len = strlen(s);
// allocate a buffer with enough room for the length info and the string
char* xfer = new char[ len + sizeof(short) ];
// copy the length info into the start of the buffer
// note: you need to hanle endian-ness of the short here.
memcpy(xfer, &len, sizeof(short));
// copy the string into the buffer
strncpy(xfer + sizeof(short), s, len);
// now xfer is the string you want to send across the wire.
// it starts with a short to identify its length.
// it is NOT null-terminated.