数据包:有效地表示不同的数据包类型

时间:2012-02-12 07:19:37

标签: java android c++ sockets multiplayer

我正在尝试设计一个服务器/客户端架构,我想要ping你们,以确定表示和解析不同类型数据包的最佳方法。每种数据包类型都需要以不同方式进行解析。下面代表我看到的数据包类型。

[*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 *和length。然后,我将使用String来表示数据。

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

有人能给我一些关于如何以不同方式实现这一点的建议吗?我不太确定是否有一个,但也许有经验丰富的人可以给我一些见解(例如他们如何在多人游戏中做到这一点)

谢谢!

3 个答案:

答案 0 :(得分:2)

我目前正在使用Protocol Buffers制作一个多线程C ++聊天服务器,用于实际的协议实现。重点是,我认为你应该使用它们:它们为你需要的每个数据包提供一个漂亮的接口,它们可以用于多种语言(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;
}

答案 1 :(得分:0)

  

但是,我觉得这不是很容易扩展,而且很难维护。

从软件开发的角度来看,一种更具可扩展性/可维护性的方法是使用通用数据格式化机制(如JSON或XML),并结合使用绑定库,它可以在消息类型和Java类之间自动绑定。

然而,这有一些缺点:

  • 这些格式包括消息中的元信息(例如字段名称,标记名称)。
  • 绑定机制通常使用反射,因此比您当前正在查看的手工构建解析/解析更慢。

答案 2 :(得分: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;
};

然后每条消息都写成了la:

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

我希望你了解我想告诉你的内容。

另外,如果您需要一些额外的帮助/信息,请告诉我们:)