ネットワーク上でバイナリデータの構築と送信
-
24-10-2019 - |
質問
Minecraftのコマンドラインクライアントを作成しています。ここにあるプロトコルには完全な仕様があります。 http://mc.kev009.com/protocol. 。事前にあなたの質問に答えるために、はい、私は少しC ++ noobです。
このプロトコルの実装にはさまざまな問題がありますが、それぞれが重要です。
- プロトコルによると、すべてのタイプはビッグエンディアンであると述べています。私のデータが小さなエンディアンであるかどうか、そしてもしそうなら、どのようにビッグエンディアンに変換するかをどのように確認すべきかわかりません。
- 文字列データ型は少し奇妙です。これは、文字列の長さを含む短いものが先行する修正されたUTF-8文字列です。これを単純なchar []アレイにどのように詰め込むべきかも、単純な文字列を変更したUTF-8の文字列に変換する方法もわかりません。
- データをBig-Endianに変換し、修正された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についてはまだわかりません。
解決
従来のアプローチは、各プロトコルメッセージの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;
}
そして文字列の場合(取り扱い Asciiz文字列と同じ方法でUTF-8を変更しました):
// 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
Iteratorの範囲は、メッセージのシリアル化と敏aserializationの両方を行うために必要な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
}
他のヒント
#1には、使用する必要があります ntohs
と友達。使用 *s
(短い)16ビット整数のバージョン、および *l
(長い)32ビット整数のバージョン。 hton*
(ネットワークのホスト)は、発信データを、あなたがいるプラットフォームのエンディアンネスとは無関係に、ビッグエンディアンに変換します。 ntoh*
(ホストへのネットワーク)は、着信データをバックバックします(再び、プラットフォームのエンディアンネスとは無関係です)
私の頭の上から...
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.