2011-03-14 14 views
4

私は、minecraft用のコマンドラインクライアントを作成しています。このプロトコルには、http://mc.kev009.com/Protocolというフルスペックがあります。あらかじめあなたの質問に答えるために、私はちょっとしたC++のノブです。ネットワーク経由でバイナリデータを作成して送信する

私はこのプロトコルを実装する際にさまざまな問題があります。

  1. プロトコルには、すべてのタイプがビッグエンディアンであると記載されています。私は自分のデータがリトルエンディアンであるかどうか、どうすればビッグエンディアンに変換するかをチェックするべきかどうか分かりません。
  2. 文字列データ型は少し奇妙なものです。これは変更されたUTF-8文字列で、その前に文字列の長さを含むshortがあります。私はこれを単純なchar []配列にどのように詰めるべきか、私の単純な文字列を変更されたUTF-8のものに変換する方法を知らない。
  3. データをビッグエンディアンに変換する方法を知っていて、変更されたUTF-8文字列を作成したとしても、これをchar []配列にパックしてパッケージとして送る方法はまだ分かりません。これまで私がしてきたのは、シンプルなASCIIネットワークである。

説明、リンク、関連する機能名と短いスニペットが大変ありがとうございます。

EDIT

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

+0

。 – GWW

+0

次回はよく見えます。 – orlp

答えて

9

伝統的なアプローチは、各プロトコルメッセージのためのC++のメッセージの構造を定義し、それをシリアル化および逆シリアル化機能を実現することです。たとえば、Login Requestは次のように表現できます。

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

と文字列のための(modified UTF-8 the same way as asciiz stringsを扱う):メッセージのメンバーがバッファにしてからコピーされるのでコピーしている間に、バイト順の逆転を行うことができます

// 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イテレータが一つだけの関数テンプレートはメッセージの両方のシリアライゼーションおよびデシリアライゼーションを行うために必要とされる範囲のような入力/出力バッファを表します。

と一緒にすべてを入れて、用法:

SO上エンディアンに関する良い質問と回答の多くはすでにあります
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 
} 
+0

あなた。サーはい。驚くばかり。 +25をお楽しみください。 – orlp

+0

デフォルトのネットワークエンディアン機能を使用しない方が良いでしょうか? http://beej.us/guide/bgnet/output/html/multipage/htonsman.html – orlp

+0

64ビットバージョンのhton関数はなく、16と32のみです。 'betoh'と' hoteb'はちょっと違っていますLinux 64ビット整数をサポートする同じ機能の固有名。ビッグエンディアンをリトルエンディアンに変換して戻すときに、 'memcpy()'の逆バージョンを使用することで、それらを完全に使用することは避けてください。 –

1

#1の場合は、ntohsとフレンドを使用する必要があります。 16ビット整数には*s(短い)バージョンを使用し、32ビット整数には*l(ロング)バージョンを使用してください。 hton*(ホストとネットワーク)は、接続しているプラ​​ットフォームのエンディアンとは無関係に、送信データをビッグエンディアンに変換し、ntoh*(ネットワークとホスト)は受信データを元に戻します(プラットフォームエンディアンとは独立しています)

+0

ありがとうございます。それらのintをchar []配列にバイト単位で分割して格納する方法はありますか? – orlp

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. 
+0

なぜこれがdownvoted、ありがとう! – orlp

+0

実際には、長さに2つの変数を宣言する必要があります.1つは、実際の長さを保持するsize_tです。少なくとも 'len'をunsigned shortにしてください。今のところ立つように、このコードは 'strlen(s)%65536> 32768'のときにはすばらしく失敗します。 – user470379

+0

良い点。あるいは、short len = boost :: numeric_cast (strlen(s));を実行して、shortに収まらない場合は例外をスローします。 – Tim

関連する問題