2012-02-12 98 views
1

我試圖設計一個服務器/客戶端體系結構,並且我想讓你們能夠確定表示和解析不同類型數據包的最佳方式。每種數據包類型都需要進行不同的解析。下面表示我會看到的數據包類型。數據包:有效地表示不同數據包類型

[*packet_type*][length][variable length data] 
*packet_type* describes the type of packet we're sending (client login, server returning authentication, data, etc) 
length describes how much data to read 
variable length data contains the info to be sent. it will be specialized based on the packet_type. the data will be variable regardless of 

我進去看了tcphdr結構,我想我可以使用相似類型的報頭爲代表* packet_type *和長度。然後,我會使用一個字符串來表示數據。

public class Packet { 
    public enum PKT_TYPE { 
      CL_REGISTER, 
      CL_LOGIN, 
      SRV_AUTH, 
      SRV_GAME_INFO, 
    } 

    PKT_TYPE _packet_type; 
    int _length; 
    String _data; 
} 

現在有一個共同的基礎,我想我可以實現每個* packet_type *的類和發送/接收方法。但是,我覺得這不是很可擴展性,並且很難維護。安(粗糙,僞)的這個例子是

public class Packet { 
... 
    public class Pkt_CL_LOGIN extends Packet { 
     String _loginname; 
     String _password; 

     public boolean send() { 
      //socket.write(CL_LOGIN, length, _loginname+_password); 
     } 
     public Pkt_CL_LOGIN parse(String data) { 
      //removed header already, so first byte will be data 
      //extract login + password 
      _loginname = login; 
      _password = password; 
      return this; 
     } 
    } 
    public Packet receive() { 
     //read from socket 
     //parse header for packet_type 
     switch (packet_type) 
      case CL_LOGIN: 
       return (new Pkt_CL_LOGIN()).parse(data); 
    } 
} 

誰能給我如何以不同的方式實現這一一些建議?我不太清楚,如果有一個,但也許更有經驗的人可以給我一些見解(比如他們是如何做到這一點在多人遊戲等)

謝謝!

回答

2

我目前做多線程C++使用Protocol Buffers的實際協議實現聊天服務器。關鍵是,我認爲你應該使用它們:它們爲你需要的每個數據包提供了一個漂亮的接口,它們可以用於很多語言(C++,Java和Python只是爲了開始,我認爲它們有一些Ruby接口, ),它們允許你創建多功能的協議,而不需要太多的努力,因爲它們消除了序列化問題,並且需要爲每個數據包編寫自己的類。此外,還有一個針對移動設備的特定「精簡」版本(如果您爲Android編碼,可能會派上用場)。

關於封包,有兩種方法,我知道的用於跟蹤當一個數據包結束的:第一種是具有固定長度的數據包,並實際發送數據包之前,第二個發送的長度。這同樣適用於數據包類型。如果你沒有很多數據包類型,你可以使用一個簡單的unsigned char(現在,這是C++,但我猜應該有一些方法可以在Java中做同樣的事情)來表示它,這會給你255數據包類型(如果你問我,多需要)。我實際上是在發送一個包含數據包長度和類型(兩者都是固定長度uint32)的固定長度的頭文件,然後再解析套接字,然後再次讀取套接字該信息隨後被相應地解析併發送到客戶機處理程序內的適當處理程序。我認爲這種方法是相當不錯的,除了事實,好...我使用了一些額外的內存白白(包類型太大)...

至於協議緩衝區的例子,你的.proto文件可能看起來有點像這樣:

message Header { 
    required fixed32 length = 1; 
    required fixed32 type = 2; // Note: don't use an enum here, as the values are serialized to varint, which simply kills your fixedness. 
} 

message Login { 
    required string nickname = 1; 
    required string password = 2; 
} 

enum ErrorType { 
    BAD_HEADER = 0; 
    WRONG_PASSWORD = 1; 
} 

message Error { 
    required ErrorType type = 1; 
    optional string message = 2; 
} 
+0

嘿朱利安。我真的很喜歡你使用Protocol Buffers的建議。這正是我所尋找的 - 這看起來很容易擴展和維護。我希望我能投票給你,但不幸的是我還沒有足夠的代表。 – John 2012-02-12 20:30:06

+0

它確定大聲笑,只是接受問題。 – 2012-02-12 22:45:40

0

不過,我喜歡這種感覺是不是很可擴展性,這將是非常難以維護。

從軟件開發的角度來看,更具擴展性/維護的方法是使用一個通用的數據格式的機制,如JSON或XML結合,讓你自動的消息類型和Java類之間結合的綁定庫。

然而,這具有兩個缺點:

  • 這些格式包括在消息中的元信息(例如字段名,標籤名稱)。
  • 綁定機制通常使用反射,因此比您目前正在查看的手動構建解析/解析的類型要慢。
+0

謝謝你的建議,斯蒂芬。我確實喜歡它很容易擴展。我在文章中沒有提到我們正在努力節省帶寬,因此元數據(如您所注意的)會增加我們發送的數據量。 – John 2012-02-12 20:37:07

0

對於一個學校項目,我必須編寫一個IRC客戶端,而且由於IRC消息格式類似於您的需求,這就是我爲我的項目所做的。

我開始了與基類的消息:

enum MSGTYPE 
{ 
    MSG_UNKNOWN = 0, 
    MSG_ADMIN, 
    MSG_AWAY, 
    MSG_CONNECT, 
    MSG_DCC, 
    ... 
} 
class Message 
{ 
public: 
    typedef Message type; 
private: 
protected: 
public: 
    virtual MSGTYPE GetMessageType() const 
    { 
     return MSG_UNKNOWN; 
    } 
    virtual type * Clone() const = 0; 
    virtual std::string Serialize() const = 0; 
    virtual bool Deserialize(std::string const &) = 0; 
}; 

然後每個消息寫入點菜:

class PrivateMessage : public Message 
{ 
public: 
    typedef PrivateMessage type; 
    const static MSGTYPE MESSAGETYPE = MSG_PRIVMSG; 
    const static std::string IDENTIFIER; 
private: 
protected: 
    std::string m_target; 
    std::string m_message; 
public: 
    static bool Serialize(type const & msg, std::string & s) 
    { 
     if(msg.GetMessageType() != type::MESSAGETYPE) return false; 
     s = msg.Serialize(); 
     return true; 
    } 
    static bool Deserialize(std::string const & s, type ** msg) 
    { 
     type tmp; 
     if(!tmp.Deserialize(s)) return false; 
     *msg = tmp.Clone(); 
     return true; 
    } 
    PrivateMessage() : m_target(), m_message() 
    { 
    } 
    PrivateMessage(std::string const & msg) : m_target(), m_message() 
    { 
     Deserialize(msg); 
    } 
    PrivateMessage(type const & o) : m_target(o.m_target), m_message(o.m_message) 
    { 
    } 
    type * Clone() const 
    { 
     return new type(*this); 
    } 
    MSGTYPE GetMessageType() const 
    { 
     return type::MESSAGETYPE; 
    } 
    std::string Serialize() const 
    { 
     std::vector<std::string> parts; 
     parts.push_back(type::IDENTIFIER); 
     parts.push_back(m_target); 
     parts.push_back(":" + m_message); 
     return String::implode(parts); 
    } 
    bool Deserialize(std::string const & msg) 
    { 
     std::vector<std::string> parts = String::explode(msg, " ", 3); 
     if(parts.empty()) return false; 
     if(parts[0] != type::IDENTIFIER) return false; 
     switch(parts.size()) 
     { 
     case 3: 
      m_message = parts[2].substr(1); 
     case 2: 
      m_target = parts[1]; 
      return true; 
     } 
     return false; 
    } 
    void SetTarget(std::string const & target) 
    { 
     m_target = target; 
    } 
    std::string GetTarget() const 
    { 
     return m_target; 
    } 
    void SetMessage(std::string const & message) 
    { 
     m_message = message; 
    } 
    std::string GetMessage() const 
    { 
     return m_message; 
    } 
}; 
std::string const PrivateMessage::IDENTIFIER = "PRIVMSG"; 

我希望你得到什麼,我試圖展示理念您。

另外,讓我知道如果你想在這方面的一些額外的幫助/信息:)

+0

有趣。這看起來與我原來的意圖類似(儘管你的架構更加深思熟慮)。所以,每個數據包類型都有一個繼承類,每個類都有一個serialize/deserialize方法。我覺得有很多可以從中學習。對於實現,你是從套接字中讀取「Message」類,然後嘗試將它轉換爲每個數據包類型,運行Deserialize(),直到它返回true爲止? – John 2012-02-12 20:52:10

+0

@John不完全,我有一個工廠,其中包含每個消息存在的序列化器(àla'msgfact-> Add(static_message_serializer ( ));')。這個工廠只公開了一個Serialize/Deserialize方法,它將調用每個包含的序列化程序的相應函數,直到其中的一個返回true。這些函數被從套接字讀取的字符串調用。 – 2012-02-12 21:53:50

+0

這很有道理。非常感謝您對此的建議! – John 2012-02-21 06:05:10