【问题标题】:Java nio channel data is not read correctlyJava nio 通道数据未正确读取
【发布时间】:2016-07-19 07:40:54
【问题描述】:

我通过 java nio 通道发送数据流,基本上是这样构建的:

<int:size of packet><int:packet id><packet data>

数据包数据以特定顺序填充不同的数据类型(数据包ID告诉如何解析它)。当我尝试在本地发送一些数据时,它工作得很好,但是当我尝试在 Windows Server 2012 上运行它时,它会读取一个无效的数据包大小值,例如负值或太大的值:

客户端输出:

Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkRequest (size: 24)
2988
-2032198748
java.lang.IllegalArgumentException
    at java.nio.ByteBuffer.allocate(Unknown Source)
    at network.ClientSocket.run(ClientSocket.java:66)
    at java.lang.Thread.run(Unknown Source)

最后的数字是读取的包大小,您可以在其中注意到一个荒谬的数字,它在尝试为其准备缓冲区时会导致异常。

服务器输出:

Sending packet: network.PacketLoginAck (size: 16)
Sending packet: network.PacketPlayerData (size: 951)
Sending packet: network.PacketWorldInfo (size: 33)
Received packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkData (size: 2988)     // This is the first package that still worked
Received packet: network.PacketChunkRequest (size: 24)
Received packet: network.PacketChunkRequest (size: 24)
Received packet: network.PacketChunkRequest (size: 24)
Received packet: network.PacketChunkRequest (size: 24)
Received packet: network.PacketChunkRequest (size: 24)
Received packet: network.PacketChunkRequest (size: 24)
Received packet: network.PacketChunkRequest (size: 24)
Received packet: network.PacketChunkRequest (size: 24)
Sending packet: network.PacketChunkData (size: 2518)
Sending packet: network.PacketChunkData (size: 2741)
Sending packet: network.PacketChunkData (size: 2966)
Sending packet: network.PacketChunkData (size: 2449)
Sending packet: network.PacketChunkData (size: 2769)
Sending packet: network.PacketChunkData (size: 1862)
Sending packet: network.PacketChunkData (size: 2526)
Sending packet: network.PacketChunkData (size: 2353)

一个PacketChunkRequest包含两个int,两个坐标,而PacketChunkData也包含这两个int,加上二进制数据,这是一个描述数据长度的int,后面跟着实际数据。

我这样发送数据:

for(Packet p : packets) {
    System.out.println("Sending packet: "+p.getClass().getName()+" (size: "+p.length()+")");

    ByteBuffer b = p.getBuffer();
    while (b.hasRemaining()) {
        clientChannel.write(b);
    }
    b.clear();

    sentPackages.add(p);
}

这是读取数据包的代码:

List<ByteBuffer> packets = new ArrayList<ByteBuffer>();

ByteBuffer bin = null;

int packetLength = 0;

while((bytesRead = channel.read(buffer)) > 0) {
    buffer.flip();
    while(buffer.remaining() > 0) {
        if(packetLength == 0) {
            if(buffer.remaining() < 4) break;

            packetLength = buffer.getInt();
            System.out.println(packetLength);        // This is the output of the length
            bin = ByteBuffer.allocate(packetLength); // This is where the error happens
        }  

        int readSize = Math.min(packetLength, buffer.remaining());

        buffer.limit(buffer.position() + readSize);

        bin.put(buffer);

        buffer.limit(bytesRead);

        packetLength -= readSize;

        if(packetLength == 0) {
            bin.flip();

            packets.add(bin);
        }
    }

    byte[] remaining = new byte[buffer.remaining()];
    for(int i = 0; buffer.remaining() > 0; i++) remaining[i] = buffer.get(); 

    buffer.clear();
    for(byte b : remaining) buffer.put(b);
}

此代码尝试通过将与包大小(包的第一个 int)一样多的字节读入单个缓冲区来重构一起发送的各个包。

此错误并非总是可重现,而是不断出现,但不是在本地(至少我从未遇到过)

【问题讨论】:

    标签: java networking nio


    【解决方案1】:

    您与发件人不同步。我没有详细分析你的接收代码,但显然它不能处理所有可能的情况,也不能正确地将数据包长度读取为整数。您正在阅读部分节奏作为长度词。你需要重新考虑你的逻辑。

    我会质疑您为什么要为此使用 NIO。尝试使用DataInputStream,依次使用readInt()readFully(),直到抛出EOFException

    【讨论】:

    • 我使用 NIO 是因为我必须同时处理多个客户端,并且只想对实际发送请求的人做出反应,而不是等待一个可能会阻止其他一切的人。那么你所说的不同步到底是什么意思?有什么文件我可以查一下吗?
    • 我的意思是您正在将数据包的一部分作为长度字读取。我所说。简单的概念,不需要文件。您在阻塞模式下使用 NIO,这与使用流没有什么不同。否则,您会通过使用读取循环而不是 select() 循环在非阻塞模式下滥用它。无论哪种方式,您都没有实现评论中所述的目标:一旦您开始为客户提供服务,所有其他人都在等待。
    • 我确实使用了一个选择循环,但它不在此代码段中。所有传入的数据包都在其他线程中处理,因此它们不会阻塞这个。
    猜你喜欢
    • 2013-10-26
    • 2015-07-25
    • 2010-11-05
    • 2023-03-06
    • 2020-10-08
    • 1970-01-01
    • 1970-01-01
    • 2014-04-04
    • 1970-01-01
    相关资源
    最近更新 更多