Пакеты: эффективное представление различных типов пакетов - PullRequest
1 голос
/ 12 февраля 2012

Я пытаюсь спроектировать архитектуру сервер / клиент, и я хотел бы поблагодарить вас, ребята, чтобы определить лучший способ представления и анализа различных типов пакетов. Каждый тип пакета должен быть проанализирован по-разному. Ниже представлен тип пакетов, которые я вижу.

[*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 и думаю, что смогу использовать заголовок аналогичного типа для представления * package_type * и длины. Затем я использовал бы 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;
}

Теперь, когда есть общая база, я решил, что могу реализовать классы и методы отправки / получения для каждого * package_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 ]

2 голосов
/ 12 февраля 2012

В настоящее время я создаю многопоточный сервер чата 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;
}
0 голосов
/ 12 февраля 2012

Для школьного проекта мне пришлось написать 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 голосов
/ 12 февраля 2012

Однако я чувствую, что это не очень масштабируемо, и его будет очень сложно поддерживать.

С точки зрения разработки программного обеспечения, более масштабируемый / обслуживаемый подход заключается в использовании общего механизма форматирования данных, такого как JSON или XML, в сочетании с библиотекой привязки, которая обеспечивает автоматическое связывание между типами сообщений и классами Java.

Однако, у этого есть несколько недостатков:

  • Эти форматы включают в сообщения мета-информацию (например, имена полей, имена тегов).
  • Механизмы связывания, как правило, используют отражение, и, следовательно, работают медленнее, чем тот тип анализа / разборки, который вы сейчас просматриваете.
...