5

在 C 中,如果您有某种类型的数据包,您通常会定义一些结构并将 char * 转换为指向该结构的指针。在此之后,您可以直接以编程方式访问网络数据包中的所有数据字段。像这样:

struct rdp_header {
  int version;
  char serverId[20];
};

当您收到网络数据包时,您可以快速执行以下操作:

char * packet;
// receive packet
rdp_header * pckt = (rdp_header * packet);
printf("Servername : %20.20s\n", pckt.serverId);

这种技术非常适用于基于 UDP 的协议,并且允许使用非常少的代码和简单的错误处理(只需检查数据包的长度)非常快速和非常有效地解析和发送数据包。是否有等效的方法,就像在 java 中一样快速地做同样的事情?还是您被迫使用基于流的技术?

4

6 回答 6

3

将您的数据包读入一个字节数组,然后从中提取您想要的位和字节。

这是一个示例,没有异常处理:

DatagramSocket s = new DatagramSocket(port);
DatagramPacket p;
byte buffer[] = new byte[4096];

while (true) {
    p = new DatagramPacket(buffer, buffer.length);
    s.receive(p);

    // your packet is now in buffer[];
    int version = buffer[0] << 24 + buffer[1] << 16 + buffer[2] < 8 + buffer[3];
    byte[] serverId = new byte[20];
    System.arraycopy(buffer, 4, serverId, 0, 20);

     // and process the rest
}

在实践中,您可能最终会使用辅助函数从字节数组中按网络顺序提取数据字段,或者正如Tom在评论中指出的那样,您可以使用 a ByteArrayInputStream(),您可以从中构造 aDataInputStream()具有读取结构化数据的方法从流中:

...

while (true) {
    p = new DatagramPacket(buffer, buffer.length);
    s.receive(p);

    ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
    DataInput di = new DataInputStream(bais);

    int version = di.readInt();
    byte[] serverId = new byte[20];
    di.readFully(serverId);
    ...
}
于 2009-01-12T21:03:38.927 回答
2

我不相信这种技术可以在 Java 中完成,除非使用 JNI 并且实际上用 C 编写协议处理程序。实现您描述的技术的另一种方法是变体记录和联合,Java 也没有。

如果您可以控制协议(它是您的服务器和客户端),您可以使用序列化对象(inc.xml)来获得数据的自动(但运行时效率不高)解析,但仅此而已。

否则,您将无法解析 Streams 或字节数组(可以视为 Streams)。

请注意,您所描述的技术非常容易出错,并且对于任何相当有趣的协议来说都是安全漏洞的来源,因此损失并不大。

于 2009-01-12T21:11:22.437 回答
1

我写了一些东西来简化这种工作。像大多数任务一样,编写工具比尝试手动完成所有事情要容易得多。

它由两个类组成,这是一个如何使用它的示例:

    // Resulting byte array is 9 bytes long.
    byte[] ba = new ByteArrayBuilder()

     .writeInt(0xaaaa5555) // 4 bytes
     .writeByte(0x55) //      1 byte
     .writeShort(0x5A5A) //   2 bytes
     .write( (new BitBuilder())  //     2 bytes---0xBA12                
            .write(3, 5) //     101      (3 bits value of 5)
            .write(2, 3) //        11    (2 bits value of 3)
            .write(3, 2) //          010 (...)
            .write(2, 0) //     00
            .write(2, 1) //       01
            .write(4, 2) //         0002
        ).getBytes();

我编写了 ByteArrayBuilder 来简单地累积位。我使用了一种方法链接模式(只是从所有方法中返回“this”),以便更容易地一起编写一堆语句。

ByteArrayBuilder 中的所有方法都是微不足道的,就像 1 或 2 行代码(我只是将所有内容都写入数据输出流)

这是构建一个数据包,但撕开一个应该不会更难。

BitBuilder 中唯一有趣的方法是这个:

public BitBuilder write(int bitCount, int value) {
    int bitMask=0xffffffff;  
    bitMask <<= bitCount;   // If bitcount is 4, bitmask is now ffffff00
    bitMask = ~bitMask;     // and now it's 000000ff, a great mask

    bitRegister <<= bitCount; // make room
    bitRegister |= (value & bitMask); // or in the value (masked for safety)
    bitsWritten += bitCount;
    return this;
}

同样,可以很容易地反转逻辑以读取数据包而不是构建数据包。

编辑:我在这个答案中提出了一种不同的方法,我将把它作为一个单独的答案发布,因为它完全不同。

于 2009-01-13T00:30:57.107 回答
1

查看 Javolution 库及其结构类,它们会满足您的要求。事实上,作者有这个确切的例子,使用 Javolution Struct 类来操作 UDP 数据包。

于 2009-04-17T15:28:41.197 回答
0

这是我在上面留下的答案的替代建议。我建议您考虑实现它,因为它的行为与 C 解决方案几乎相同,您可以按名称从数据包中选择字段。

您可以从一个外部文本文件开始,如下所示:

OneByte,       1
OneBit,       .1
TenBits,      .10
AlsoTenBits,  1.2
SignedInt,    +4  

它可以指定数据包的整个结构,包括可能重复的字段。语言可以根据您的需要简单或复杂——

你会创建一个像这样的对象:

new PacketReader packetReader("PacketStructure.txt", byte[] packet);

您的构造函数将遍历 PacketStructure.txt 文件并将每个字符串存储为哈希表的键,并将其数据的确切位置(位偏移量和大小)存储为数据。

一旦你创建了一个对象,传入了 bitStructure 和一个数据包,你就可以使用如下语句随机访问数据:

int x=packetReader.getInt("AlsoTenBits");

还要注意,这些东西的效率会比 C 结构低得多,但没有你想象的那么多——它可能仍然比你需要的效率高很多倍。如果处理得当,规范文件只会被解析一次,因此您只会对从数据包中读取的每个值进行一次散列查找和一些二进制操作的轻微打击——一点也不差。

例外情况是,如果您正在从高速连续流中解析数据包,即使这样,我也怀疑快速的网络可能会淹没速度较慢的 CPU。

于 2009-01-13T21:53:58.357 回答
0

简短的回答,不,你不能那么容易做到。

更长的答案,如果你可以使用Serializable对象,你可以连接InputStream到一个ObjectInputStream并使用它来反序列化你的对象。但是,这需要您对协议有一定的控制权。如果您使用 TCP ,它也更容易工作Socket。如果您使用 UDP DatagramSocket,则需要从数据包中获取数据,然后将其输入ByteArrayInputStream.

如果您无法控制协议,您可能仍然可以使用上述反序列化方法,但您可能必须实现readObject()andwriteObject()方法,而不是使用提供给您的默认实现。如果您需要使用其他人的协议(比如因为您需要与本机程序互操作),这可能是您将找到的最简单的解决方案。

另外,请记住 Java 在内部对字符串使用 UTF-16,但我不确定它是否以这种方式序列化它们。无论哪种方式,在将字符串来回传递给非 Java 程序时都需要非常小心。

于 2009-01-13T22:12:31.940 回答