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