4

Minecraftのコマンドラインクライアントを作成しています。ここで見つけることができるプロトコルに関する完全な仕様があります:http://mc.kev009.com/Protocol。事前にあなたの質問に答えるために、はい、私は少しC++の初心者です。

このプロトコルの実装にはさまざまな問題がありますが、それぞれが重要です。

  1. プロトコルは、すべてのタイプがビッグエンディアンであると述べています。データがリトルエンディアンであるかどうかを確認する方法と、そうである場合はビッグエンディアンに変換する方法がわかりません。
  2. 文字列のデータ型は少し奇妙なものです。これは、文字列の長さを含むshortが前に付いた、変更されたUTF-8文字列です。これを単純なchar[]配列にパックする方法も、単純な文字列を変更されたUTF-8文字列に変換する方法もわかりません。
  3. データをビッグエンディアンに変換して変更されたUTF-8文字列を作成する方法を知っていたとしても、これをchar[]配列にパックしてパッケージとして送信する方法がわかりません。私が以前にしたことは、プレーンASCIIである単純なHTTPネットワーキングだけです。

説明、リンク、関連する関数名、短いスニペットは大歓迎です!

編集

1と3は今答えられます。1はuser470379によって以下に答えられます。3は、私が非常にうまくやりたいことを説明するこの素晴らしいスレッドによって答えられます:http: //cboard.cprogramming.com/networking-device-communication/68196-sending-non-char*-data.htmlわかりませんただし、変更されたUTF-8についてはまだです。

4

3 に答える 3

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;
};

ここで、シリアル化機能が必要です。まず、整数と文字列のシリアル化関数が必要です。これらはのメンバーのタイプだから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イテレーター範囲として表すと、メッセージのシリアル化と逆シリアル化の両方を実行するために必要な関数テンプレートは1つだけです。

そして、すべてをまとめると、使用法:

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
}
于 2011-03-14T21:18:11.917 に答える
1

#1の場合はntohs、友達を使用する必要があります。*s16ビット整数には(短い)バージョンを使用し、*l32ビット整数には(長い)バージョンを使用します。(hton*ホストからネットワーク)は、使用しているプラ​​ットフォームのエンディアンに関係なく、発信データをビッグエンディアンに変換し、ntoh*(ネットワークからホスト)は、着信データを(プラットフォームのエンディアンに関係なく)元に戻します。

于 2011-03-14T19:52:07.420 に答える
1

頭のてっぺんから...

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.
于 2011-03-14T20:30:02.233 に答える