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

Packets: Effectively representing different packet types

本文关键字:数据包 类型 表示 有效地      更新时间:2023-10-16

我正在尝试设计一个服务器/客户端架构,我想对你们进行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*和长度。然后,我将使用字符串来表示数据。

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

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

谢谢!

我目前正在制作一个多线程C++聊天服务器,使用协议缓冲区来实现实际的协议。重点是,我认为你应该使用它们:它们为你所需要的每个数据包提供了一个漂亮的接口,它们可以在多种语言中使用(C++、Java和Python刚刚开始,我认为还有一些Ruby接口),它们允许你无需太多努力就可以创建多功能协议,因为它们消除了序列化问题,也消除了为每个数据包编写自己的类的需要。此外,还有一个针对移动设备的特定"lite"版本(如果你为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;
}

然而,我觉得这不是很可扩展,而且很难维护。

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

然而,这有几个缺点:

  • 这些格式包括消息中的元信息(例如字段名、标签名)
  • 绑定机制通常使用反射,因此比您目前正在研究的手工构建解析/解包慢

对于一个学校项目,我必须编写一个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";

我希望你知道我想给你看什么

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