如何从Java中获取网络数据包数据
在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的协议非常有用,并且允许使用非常少的代码进行非常快速和非常有效的数据包解析和发送,以及简单的error handling(只需检查数据包的长度)。 有没有相同的,同样快速的方式在java中做同样的事情? 或者你被迫使用基于流的技术?
将数据包读入字节数组,然后从中提取所需的位和字节。
这是一个示例,没有exception处理:
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在注释中指出的那样,您可以使用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); ... }
我不相信这种技术可以在Java中完成,不能使用JNI并且实际上在C中编写协议处理程序。您描述的技术的另一种方式是变体记录和联合,Java也没有。
如果您已经控制了协议(它是您的服务器和客户端),您可以使用序列化对象(包括xml)来获得数据的自动化(但不是那么运行时效率)解析,但这就是它。
否则,您将无法解析Streams或字节数组(可以将其视为Streams)。
请注意,您所描述的技术非常容易出错,并且对于任何合理有趣的协议都存在安全漏洞的来源,所以它并没有那么大的损失。
我写了一些东西来简化这种工作。 像大多数任务一样,编写工具比尝试手工完成任务要容易得多。
它由两个类组成,以下是如何使用它的示例:
// 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; }
同样,逻辑可以非常容易地被反转以读取数据包而不是构建数据包。
编辑:我在这个答案中提出了一个不同的方法,我将把它作为一个单独的答案发布,因为它完全不同。
查看Javolution库及其结构类,它们将满足您的要求。 事实上,作者有这个确切的例子,使用Javolution Struct类来操作UDP数据包。
这是我上面留下的答案的替代提案。 我建议你考虑实现它,因为它的行为与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。
简短的回答,不,你不能轻易做到这一点。
更长的答案是,如果可以使用Serializable
对象,则可以将InputStream
挂接到ObjectInputStream
并使用它来反序列化对象。 但是,这需要您对协议进行一些控制。 如果使用TCP Socket
它也会更容易。 如果使用UDP DatagramSocket
,则需要从数据包中获取数据,然后将其提供给ByteArrayInputStream
。
如果你无法控制协议,你仍然可以使用上面的反序列化方法,但是你可能不得不实现readObject()
和writeObject()
方法,而不是使用给定的默认实现。您。 如果您需要使用其他人的协议(比如因为您需要与本机程序互操作),这可能是您将要找到的最简单的解决方案。
另外,请记住Java在内部使用UTF-16作为字符串,但我不确定它是否以这种方式序列化它们。 无论哪种方式,在将字符串来回传递给非Java程序时都需要非常小心。