如何从Java中获取网络数据包数据

时间:2021-07-29 23:54:12

In C if you have a certain type of packet, what you generally do is define some struct and cast the char * into a pointer to the struct. After this you have direct programmatic access to all data fields in the network packet. Like so :

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

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

When you get a network packet you can do the following quickly :

获得网络数据包后,您可以快速执行以下操作:

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

This technique works really great for UDP based protocols, and allows for very quick and very efficient packet parsing and sending using very little code, and trivial error handling (just check the length of the packet). Is there an equivalent, just as quick way in java to do the same ? Or are you forced to use stream based techniques ?

这种技术对基于UDP的协议非常有用,并且允许使用非常少的代码进行非常快速和非常有效的数据包解析和发送,以及简单的错误处理(只需检查数据包的长度)。有没有相同的,同样快速的方式在java中做同样的事情?或者你*使用基于流的技术?

6 个解决方案

#1


Read your packet into a byte array, and then extract the bits and bytes you want from that.

将数据包读入字节数组,然后从中提取所需的位和字节。

Here's a sample, sans exception handling:

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

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
}

In practise you'll probably end up with helper functions to extract data fields in network order from the byte array, or as Tom points out in the comments, you can use a ByteArrayInputStream(), from which you can construct a DataInputStream() which has methods to read structured data from the stream:

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

...

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

#2


I don't believe this technique can be done in Java, short of using JNI and actually writing the protocol handler in C. The other way to do the technique you describe is variant records and unions, which Java doesn't have either.

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

If you had control of the protocol (it's your server and client) you could use serialized objects (inc. xml), to get the automagic (but not so runtime efficient) parsing of the data, but that's about it.

如果您已经控制了协议(它是您的服务器和客户端),您可以使用序列化对象(包括xml)来获得数据的自动化(但不是那么运行时效率)解析,但这就是它。

Otherwise you're stuck with parsing Streams or byte arrays (which can be treated as Streams).

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

Mind you the technique you describe is tremendously error prone and a source of security vulnerabilities for any protocol that is reasonably interesting, so it's not that great a loss.

请注意,您所描述的技术非常容易出错,并且对于任何合理有趣的协议都存在安全漏洞的来源,所以它并没有那么大的损失。

#3


I wrote something to simplify this kind of work. Like most tasks, it was much easier to write a tool than to try to do everything by hand.

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

It consisted of two classes, Here's an example of how it was used:

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

    // 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();

I wrote the ByteArrayBuilder to simply accumulate bits. I used a method chaining pattern (Just returning "this" from all methods) to make it easier to write a bunch of statements together.

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

All the methods in the ByteArrayBuilder were trivial, just like 1 or 2 lines of code (I just wrote everything to a data output stream)

ByteArrayBuilder中的所有方法都很简单,就像1或2行代码一样(我只是将所有内容写入数据输出流)

This is to build a packet, but tearing one apart shouldn't be any harder.

这是建立一个数据包,但拆分一个不应该更难。

The only interesting method in BitBuilder is this one:

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

Again, the logic could be inverted very easily to read a packet instead of build one.

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

edit: I had proposed a different approach in this answer, I'm going to post it as a separate answer because it's completely different.

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

#4


Look at the Javolution library and its struct classes, they will do just what you are asking for. In fact, the author has this exact example, using the Javolution Struct classes to manipulate UDP packets.

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

#5


This is an alternate proposal for an answer I left above. I suggest you consider implementing it because it would act pretty much the same as a C solution where you could pick fields out of a packet by name.

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

You might start it out with an external text file something like this:

您可以使用外部文本文件启动它,如下所示:

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

It could specify the entire structure of a packet, including fields that may repeat. The language could be as simple or complicated as you need--

它可以指定数据包的整个结构,包括可能重复的字段。语言可能像你需要的那样简单或复杂 -

You'd create an object like this:

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

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

Your constructor would iterate over the PacketStructure.txt file and store each string as the key of a hashtable, and the exact location of it's data (both bit offset and size) as the data.

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

Once you created an object, passing in the bitStructure and a packet, you could randomly access the data with statements as straight-forward as:

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

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

Also note, this stuff would be much less efficient than a C struct, but not as much as you might think--it's still probably many times more efficient than you'll need. If done right, the specification file would only be parsed once, so you would only take the minor hit of a single hash lookup and a few binary operations for each value you read from the packet--not bad at all.

还要注意,这些东西的效率远远低于C结构,但不如你想象的那么多 - 它的效率可能仍然比你需要的高很多倍。如果操作正确,那么规范文件只会被解析一次,因此您只需要对从数据包中读取的每个值进行单次哈希查找和一些二进制操作的轻微命中 - 一点也不差。

The exception is if you are parsing packets from a high-speed continuous stream, and even then I doubt a fast network could flood even a slowish CPU.

例外情况是,如果您正在从高速连续流中解析数据包,即便如此,我怀疑快速网络是否会泛滥甚至是缓慢的CPU。

#6


Short answer, no you can't do it that easily.

简短的回答,不,你不能轻易做到这一点。

Longer answer, if you can use Serializable objects, you can hook your InputStream up to an ObjectInputStream and use that to deserialize your objects. However, this requires you have some control over the protocol. It also works easier if you use a TCP Socket. If you use a UDP DatagramSocket, you will need to get the data from the packet and then feed that into a ByteArrayInputStream.

更长的答案是,如果可以使用Serializable对象,则可以将InputStream挂接到ObjectInputStream并使用它来反序列化对象。但是,这需要您对协议进行一些控制。如果使用TCP套接字,它也会更容易。如果使用UDP DatagramSocket,则需要从数据包中获取数据,然后将其提供给ByteArrayInputStream。

If you don't have control over the protocol, you may be able to still use the above deserialization method, but you're probably going to have to implement the readObject() and writeObject() methods rather than using the default implementation given to you. If you need to use someone else's protocol (say because you need to interop with a native program), this is likely the easiest solution you are going to find.

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

Also, remember that Java uses UTF-16 internally for strings, but I'm not certain that it serializes them that way. Either way, you need to be very careful when passing strings back and forth to non-Java programs.

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

#1


Read your packet into a byte array, and then extract the bits and bytes you want from that.

将数据包读入字节数组,然后从中提取所需的位和字节。

Here's a sample, sans exception handling:

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

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
}

In practise you'll probably end up with helper functions to extract data fields in network order from the byte array, or as Tom points out in the comments, you can use a ByteArrayInputStream(), from which you can construct a DataInputStream() which has methods to read structured data from the stream:

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

...

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

#2


I don't believe this technique can be done in Java, short of using JNI and actually writing the protocol handler in C. The other way to do the technique you describe is variant records and unions, which Java doesn't have either.

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

If you had control of the protocol (it's your server and client) you could use serialized objects (inc. xml), to get the automagic (but not so runtime efficient) parsing of the data, but that's about it.

如果您已经控制了协议(它是您的服务器和客户端),您可以使用序列化对象(包括xml)来获得数据的自动化(但不是那么运行时效率)解析,但这就是它。

Otherwise you're stuck with parsing Streams or byte arrays (which can be treated as Streams).

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

Mind you the technique you describe is tremendously error prone and a source of security vulnerabilities for any protocol that is reasonably interesting, so it's not that great a loss.

请注意,您所描述的技术非常容易出错,并且对于任何合理有趣的协议都存在安全漏洞的来源,所以它并没有那么大的损失。

#3


I wrote something to simplify this kind of work. Like most tasks, it was much easier to write a tool than to try to do everything by hand.

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

It consisted of two classes, Here's an example of how it was used:

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

    // 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();

I wrote the ByteArrayBuilder to simply accumulate bits. I used a method chaining pattern (Just returning "this" from all methods) to make it easier to write a bunch of statements together.

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

All the methods in the ByteArrayBuilder were trivial, just like 1 or 2 lines of code (I just wrote everything to a data output stream)

ByteArrayBuilder中的所有方法都很简单,就像1或2行代码一样(我只是将所有内容写入数据输出流)

This is to build a packet, but tearing one apart shouldn't be any harder.

这是建立一个数据包,但拆分一个不应该更难。

The only interesting method in BitBuilder is this one:

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

Again, the logic could be inverted very easily to read a packet instead of build one.

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

edit: I had proposed a different approach in this answer, I'm going to post it as a separate answer because it's completely different.

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

#4


Look at the Javolution library and its struct classes, they will do just what you are asking for. In fact, the author has this exact example, using the Javolution Struct classes to manipulate UDP packets.

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

#5


This is an alternate proposal for an answer I left above. I suggest you consider implementing it because it would act pretty much the same as a C solution where you could pick fields out of a packet by name.

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

You might start it out with an external text file something like this:

您可以使用外部文本文件启动它,如下所示:

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

It could specify the entire structure of a packet, including fields that may repeat. The language could be as simple or complicated as you need--

它可以指定数据包的整个结构,包括可能重复的字段。语言可能像你需要的那样简单或复杂 -

You'd create an object like this:

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

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

Your constructor would iterate over the PacketStructure.txt file and store each string as the key of a hashtable, and the exact location of it's data (both bit offset and size) as the data.

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

Once you created an object, passing in the bitStructure and a packet, you could randomly access the data with statements as straight-forward as:

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

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

Also note, this stuff would be much less efficient than a C struct, but not as much as you might think--it's still probably many times more efficient than you'll need. If done right, the specification file would only be parsed once, so you would only take the minor hit of a single hash lookup and a few binary operations for each value you read from the packet--not bad at all.

还要注意,这些东西的效率远远低于C结构,但不如你想象的那么多 - 它的效率可能仍然比你需要的高很多倍。如果操作正确,那么规范文件只会被解析一次,因此您只需要对从数据包中读取的每个值进行单次哈希查找和一些二进制操作的轻微命中 - 一点也不差。

The exception is if you are parsing packets from a high-speed continuous stream, and even then I doubt a fast network could flood even a slowish CPU.

例外情况是,如果您正在从高速连续流中解析数据包,即便如此,我怀疑快速网络是否会泛滥甚至是缓慢的CPU。

#6


Short answer, no you can't do it that easily.

简短的回答,不,你不能轻易做到这一点。

Longer answer, if you can use Serializable objects, you can hook your InputStream up to an ObjectInputStream and use that to deserialize your objects. However, this requires you have some control over the protocol. It also works easier if you use a TCP Socket. If you use a UDP DatagramSocket, you will need to get the data from the packet and then feed that into a ByteArrayInputStream.

更长的答案是,如果可以使用Serializable对象,则可以将InputStream挂接到ObjectInputStream并使用它来反序列化对象。但是,这需要您对协议进行一些控制。如果使用TCP套接字,它也会更容易。如果使用UDP DatagramSocket,则需要从数据包中获取数据,然后将其提供给ByteArrayInputStream。

If you don't have control over the protocol, you may be able to still use the above deserialization method, but you're probably going to have to implement the readObject() and writeObject() methods rather than using the default implementation given to you. If you need to use someone else's protocol (say because you need to interop with a native program), this is likely the easiest solution you are going to find.

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

Also, remember that Java uses UTF-16 internally for strings, but I'm not certain that it serializes them that way. Either way, you need to be very careful when passing strings back and forth to non-Java programs.

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