gpt4 book ai didi

java - 字节流中协议(protocol)数据单元的动态识别和处理

转载 作者:搜寻专家 更新时间:2023-10-31 19:33:48 25 4
gpt4 key购买 nike

我为一个小型多人游戏实现了一个协议(protocol)。它基于字节,因此要反序列化接收到的消息,我必须遍历字节流并逐位解析它。在拥有所有字节并知道消息类型之后,我将字节扔到反向构造器中,该构造器从原始字节构造协议(protocol)数据单元。

整个过程非常丑陋,不是真正的面向对象,并且具有不可读的if/else代码。
我必须为添加的每个协议(protocol)数据单元(pdu)实现reverseConstructor(byte[] bytes)。每个pdu都定义某种模式的方法(例如,模式= [1字节int(id = x),x字节ascii字符串,4字节 double 数]),并且使用该模式对字节进行处理更优雅。

我在这里有关于使用Google的protobuf的提示(显然,它们不符合我的需求,因为我必须更改协议(protocol)以遵守protobuf标准)。

信息

我无法更改协议(protocol)。有两种不同的方案(我不想希望同时甚至在同一程序中支持它们):

  • 协议(protocol)数据单元的长度字段编码在头
  • 协议(protocol)数据单元没有长度字段,但是可以在消息结束时/消息结束处从消息类型派生一个。

  • 我个人是长度字段的粉丝。但是有时您必须遵守别人设计的协议(protocol)。因此,该协议(protocol)是固定的。它们都有一个 header ,其中包含协议(protocol)ID,唯一消息ID,以及在第一种情况下的长度字段。

    问题

    谁能给我一个非常小的示例,其中包含两个简单的协议(protocol)数据单元,这些数据单元通过有效的通用接收方法进行了解析?我在protobuf教程中找到的唯一示例是以下类型:用户a发送消息x,用户b期望消息X并可以对其进行反序列化而不会出现问题。

    但是,如果用户b必须为消息x,y和z做好准备,该怎么办。在没有太多代码重复的情况下,如何以一种智能的方式处理这种情况。

    我还希望获得一些设计原则的提示,这些提示使我能够在不使用extern库的情况下实现更大的代码。

    编辑

    我认为这是要走的路。您可以找到更多的代码 here
    动态读取字节,直到找到对象为止,然后重置缓冲区的位置。
                    while (true) {
    if (buffer.remaining() < frameLength) {
    buffer.reset();
    break;
    }
    if (frameLength > 0) {
    Object resultObj = prototype.newBuilderForType().mergeFrom(buffer.array(), buffer.arrayOffset() + buffer.position(), frameLength).build();
    client.fireMessageReceived(resultObj);
    buffer.position(buffer.position() + frameLength);
    buffer.mark();
    }
    if (buffer.remaining() > fieldSize) {
    frameLength = getFrameLength(buffer);
    } else {
    break;
    }
    }

    JavaDoc- 合并来自

    Parse data as a message of this type and merge it with the message being built. This is just a small wrapper around MessageLite.Builder.mergeFrom(CodedInputStream). https://developers.google.com/protocol-buffers/docs/reference/java/com/google/protobuf/Message.Builder#mergeFrom(byte[])



    问题是这种类型的零件消息,但是应该可以使用通用方法来解决此问题。

    示例

    这是一个示例协议(protocol)数据单元。它有一个长度字段。在另一种情况下,pdu没有长度字段。该pdu具有可变大小。也有固定大小的pdus。

    为了完整起见。在此,协议(protocol)数据单元中字符串的表示形式。

    最佳答案

    1.协议(protocol)设计

    坦白说,创建第一个协议(protocol)实现而没有任何进一步的改变是一个常见的错误。作为练习,让我们尝试设计灵活的协议(protocol)。

    基本上,这个想法是将多个帧彼此封装在一起。请注意,您可以使用有效负载ID ,因此很容易识别序列中的下一帧。

    您可以使用Wireshark来查看现实生活中的协议(protocol)通常遵循相同的原理。

    这种方法大大简化了数据包的剖析,但是仍然可以处理其他协议(protocol)。

    2.协议(protocol)解码(解剖)

    我花了很多时间为以前的公司开发下一代网络分析仪。

    无法透露所有细节,但是关键功能之一是灵活的协议(protocol)栈,能够识别协议(protocol)框架。 RTP是一个很好的例子,因为在较低层(通常是UDP)上没有任何提示,下一帧是RTP帧。开发了特殊虚拟机来执行解剖器和控制过程。

    好消息是我的小型个人项目带有基于Java的解剖器(为了节省多行,我将跳过一些javadocs)。

    /**
    * High-level dissector contract definition. Dissector is meant to be a simple
    * protocol decoder, which analyzes protocol binary image and produces number
    * of fields.
    *
    * @author Renat.Gilmanov
    */
    public interface Dissector {

    /**
    * Returns dissector type.
    */
    DissectorType getType();

    /**
    * Verifies packet data belongs to the protocol represented by this dissector.
    */
    boolean isProtocol(DataInput input, Dissection dissection);

    /**
    * Performs the dissection.
    */
    Dissection dissect(DataInput input, Dissection dissection);

    /**
    * Returns a protocol which corresponds to the current dissector.
    *
    * @return a protocol instance
    */
    Protocol getProtocol();
    }

    协议(protocol)本身知道上层协议(protocol),因此当没有直接提示可用时,可以遍历已知协议(protocol)并使用isProtocol方法来识别下一帧。
    public interface Protocol {

    // ...

    List<Protocol> getUpperProtocols(); }

    正如我所说的,RTP协议(protocol)处理起来有些棘手:

    因此,让我们检查一下实现细节。验证基于有关该协议(protocol)的几个已知事实:
    /**
    * Verifies current frame belongs to RTP protocol.
    *
    * @param input data input
    * @param dissection initial dissection
    * @return true if protocol frame is RTP
    */
    @Override
    public final boolean isProtocol(final DataInput input, final Dissection dissection) {
    int available = input.available();
    byte octet = input.getByte();
    byte version = getVersion(octet);
    byte octet2 = input.getByte(1);
    byte pt = (byte) (octet2 & 0x7F);

    return ((pt < 0x47) & (RTP_VERSION == version));
    }

    解剖只是一组基本操作:

    public final Dissection dissect(DataInput input,Dissection d){
        // --- protocol header --------------------------------
    final byte octet1 = input.getByte(0);
    final byte version = getVersion(octet1);
    final byte p = (byte) ((octet1 & 0x20) >> 5);
    final byte x = (byte) ((octet1 & 0x10) >> 4);
    final byte cc = (byte) ((octet1 & 0x0F));

    //...

    // --- seq --------------------------------------------
    final int seq = (input.getInt() & 0x0000FFFF);
    final int timestamp = input.getInt();
    final int ssrc = input.getInt();

    最后,您可以定义一个协议(protocol)栈:
    public interface ProtocolStack {

    String getName();

    Protocol getRootProtocol();

    Dissection dissect(DataInput input, Dissection dissection, DissectOptions options);
    }

    在后台,它处理所有复杂性并逐帧解码数据包。最大的挑战是使解剖过程防弹,稳定。使用这种或类似的方法,您将能够组织协议(protocol)解码代码。 isProtocol 的正确实现很可能会允许您处理不同的版本,依此类推。无论如何,我不会说这种方法很简单,但是它提供了很多灵活性和控制能力。

    3.是否有通用解决方案?

    是的,有 ASN.1:

    Abstract Syntax Notation One (ASN.1) is a standard and notation that describes rules and structures for representing, encoding, transmitting, and decoding data in telecommunications and computer networking. The formal rules enable representation of objects that are independent of machine-specific encoding techniques. Formal notation makes it possible to automate the task of validating whether a specific instance of data representation abides by the specifications. In other words, software tools can be used for the validation.



    这是使用ASN.1定义的协议(protocol)的示例:
    FooProtocol DEFINITIONS ::= BEGIN

    FooQuestion ::= SEQUENCE {
    trackingNumber INTEGER,
    question IA5String
    }

    FooAnswer ::= SEQUENCE {
    questionNumber INTEGER,
    answer BOOLEAN
    }

    END

    顺便说一句,有 Java Asn.1 Compiler可用:

    JAC (Java Asn1 Compiler) is a tool for you if you want to (1)parse your asn1 file (2)create .java classes and (3)encode/decode instances of your classes. Just forget all asn1 byte streams, and take the advantage of OOP! BER, CER and DER are all supported.



    最后

    我通常建议执行几个简单的PoC,以找到最佳的解决方案。我决定不使用ASN.1来降低复杂性并有一些优化的余地,但这可能对您有所帮助。

    无论如何,请尝试一切并让我们知道结果:)

    您还可以检查以下主题: Efficient decoding of binary and text structures (packets)

    4.更新:双向方法

    很抱歉,我回答了很长的时间。我只希望您有足够的选择来找到最佳的解决方案。回答有关双向方法的问题:
  • 选项1 :您可以使用对称序列化方法:定义DataOutput,编写序列化逻辑-完成。我只是建议浏览一下BerkeleyDB API和TupleBinding。它确实解决了相同的问题,提供了对存储/还原过程的完全控制。

  • This class takes care of converting the entries to/from TupleInput and TupleOutput objects. Its two abstract methods must be implemented by a concrete subclass to convert between tuples and key or data objects.


    entryToObject(TupleInput)
    objectToEntry(Object,TupleOutput)
  • 选项2 :最通用的方法是定义包含一组字段的结构。每个字段都需要以下信息:
  • 名称
  • 类型
  • 大小(位)

  • 例如,对于RTP,它将如下所示:
    Version:          byte (2 bits)
    Padding: bool (1 bit)
    Extension: bool (1 bit)
    CSRC Count: byte (4 bits)
    Marker: bool (1 bit)
    Payload Type: byte (7 bits)
    Sequence Number: int (16 bits)

    有了它,您可以定义读取/写入此类结构的通用方法。我知道最近的工作示例是 Javolution Struct。请仔细阅读,他们有一个非常好的例子:
    class Clock extends Struct { // Hardware clock mapped to memory.
    Unsigned16 seconds = new Unsigned16(5); // unsigned short seconds:5 bits
    Unsigned16 minutes = new Unsigned16(5); // unsigned short minutes:5 bits
    Unsigned16 hours = new Unsigned16(4); // unsigned short hours:4 bits
    ...
    }

    关于java - 字节流中协议(protocol)数据单元的动态识别和处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18270311/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com