How to get data out of network packet data in Java

走远了吗. 提交于 2019-12-04 08:21:44
Alnitak

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:

...

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

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.

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.

Otherwise you're stuck with parsing Streams or byte arrays (which can be treated as 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.

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.

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)

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

The only interesting method in BitBuilder is this one:

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.

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.

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.

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.

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

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.

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.

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.

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!