通过网络构建和发送二进制数据

orl*_*rlp 4 c++ binary networking minecraft

我正在为我的世界创建一个命令行客户端.有关该协议的完整规范,可在此处找到:http://mc.kev009.com/Protocol.要事先回答你的问题,是的,我有点像C++菜鸟.

我在实现这个协议时遇到了各种各样的问题,每个问题都很关键

  1. 协议说所有类型都是big-endian.我不知道如何检查我的数据是否为little-endian,如果是,则如何转换为big-endian.
  2. 字符串数据类型有点奇怪.它是一个修改过的UTF-8字符串,前面是一个包含字符串长度的short.我不知道如何将它打包成一个简单的char []数组,也不知道如何将我的简单字符串转换为修改过的UTF-8数组.
  3. 即使我知道如何将我的数据转换为big-endian并创建修改后的UTF-8字符串,我仍然不知道如何将其打包到char []数组中并将其作为包发送.我之前所做的只是简单的HTTP网络,它是纯ASCII.

解释,链接,相关的功能名称和简短的片段非常感谢!

编辑

现在回答1和3.用户470379在下面回答了图1.这个AWESOME线程回答了3,它解释了我想要做的很好:http://cboard.cprogramming.com/networking-device-communication/68196-sending-non-char*-data.html我不确定关于修改后的UTF-8但是.

Max*_*kin 9

传统方法是为每个协议消息定义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;
};
Run Code Online (Sandbox Code Playgroud)

现在需要序列化功能.首先,它需要整数和字符串的序列化函数,因为这些是成员的类型LoginRequest.

整数序列化函数需要与big-endian表示进行转换.由于消息的成员被复制到缓冲区和从缓冲区复制,因此可以在复制时完成字节顺序的反转:

#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;
}
Run Code Online (Sandbox Code Playgroud)

对于字符串(处理修改后的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;
}
Run Code Online (Sandbox Code Playgroud)

还有几个辅助函子:

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);
    }
};
Run Code Online (Sandbox Code Playgroud)

现在使用这些原始函数,我们可以很容易地序列化和反序列化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());
}
Run Code Online (Sandbox Code Playgroud)

使用上面的帮助函子并将输入/输出缓冲区表示为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
}
Run Code Online (Sandbox Code Playgroud)