2011-03-14 101 views
4

我正在爲minecraft創建一個命令行客戶端。有關協議的完整規範可以在這裏找到:http://mc.kev009.com/Protocol。要事先回答你的問題,是的,我有點C++ noob。通過網絡構建和發送二進制數據

我在實現這個協議時遇到了各種問題,其中每個都很重要。

  1. 該協議說,所有類型都是big-endian。我不知道如何檢查我的數據是否是小端,如果是,如何轉換成大端。
  2. 字符串數據類型有點奇怪。它是一個修改過的UTF-8字符串,前面是一個包含字符串長度的短字符。我不知道如何將它包裝到一個簡單的char []數組中,也不知道如何將我的簡單字符串轉換爲修改後的UTF-8。
  3. 即使我知道如何將數據轉換爲big-endian並創建修改後的UTF-8字符串,我仍然不知道如何將它們打包到char []數組中,並將其作爲一個包發送。我之前所做的只是簡單的HTTP網絡,它是純ASCII的。

解釋,鏈接,相關函數名稱和短片段非常感謝!

EDIT

1和3,現在被應答。 1在下面由user470379回答。 3被這個AWESOME線程解釋了我想要做得很好:http://cboard.cprogramming.com/networking-device-communication/68196-sending-non-char*-data.html但我不確定修改後的UTF-8。

+0

已經有很多關於對SO字節序好問題和答案。 – GWW 2011-03-14 19:50:17

+0

下次我會更好看。 – orlp 2011-03-14 19:56:58

回答

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迭代器的範圍內。

,並把所有的一起,用法:

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 2011-03-14 21:26:10

+0

雖然不會使用默認網絡endian功能會更好嗎? http://beej.us/guide/bgnet/output/html/multipage/htonsman.html – orlp 2011-03-14 21:57:05

+0

沒有64位版本的hton-functions,只有16和32.'betoh'和'hoteb'只是不同的Linux - 具有64位整數支持的相同功能的特定名稱。在將big-endian轉換成little-endian時,你可以避免使用'memcpy()'的反轉版本。 – 2011-03-14 22:14:17

1

對於#1,您需要使用ntohs和朋友。對於16位整數使用*s(簡寫)版本,對於32位整數使用*l(長)版本。 hton*(網絡主機)將輸出數據轉換爲大端,而不依賴於您所在平臺的字節序列號,並且網絡到主機將會將輸入數據轉換回來(再次獨立於平臺排序)

+0

謝謝。任何想法如何通過分割字節將這些int存儲在char []數組中? – orlp 2011-03-14 20:15:07

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 2011-03-14 20:42:54

+0

你真的應該爲長度聲明兩個變量,一個是size_t來保存實際長度;或者至少使'len'成爲未簽名的短片。就像現在這樣,當strlen(s)%65536> 32768'時,這段代碼將會失敗。 – user470379 2011-03-15 00:51:33

+0

好點。或者我們可以做'short len = boost :: numeric_cast (strlen(s));'如果它不適合簡寫,它將拋出異常。 – Tim 2011-03-15 01:22:21