哦,我希望TCP像UDP一样是基于数据包的!但是,事实并非如此,因此我正在尝试实现自己的数据包层。到目前为止,这是一系列事件(忽略写数据包)

哦,我的数据包的结构非常简单:两个无符号字节表示长度,然后是byte [length]数据。 (我无法想象它们是否更复杂,我会在if语句中尽我所能!)

  • Server处于无限循环中,接受连接并将它们添加到Connection列表中。
  • PacketGatherer(另一个线程)使用Selector来确定准备读取哪些Connection.SocketChannel
  • 它遍历结果并将每个Connection告诉read()
  • 每个Connection都有一部分IncomingPacket和一系列Packet,它们已经被完全读取并等待处理。
  • read()上:
  • 告诉部分IncomingPacket读取更多数据。 (下面的IncomingPacket.readData)
  • 如果已完成读取(IncomingPacket.complete()),请从中制作一个Packet并将Packet粘贴到列表中等待处理,然后将其替换为新的IncomingPacket

  • 这有几个问题。首先,一次仅读取一个数据包。如果IncomingPacket仅需要再一个字节,则此遍仅读取一个字节。这当然可以通过循环来解决,但是会变得有点复杂,我想知道是否有更好的整体方法。

    第二,IncomingPacket中的逻辑有点疯狂,以便能够读取两个字节的长度,然后读取实际数据。这是代码,为了便于快速阅读而精简:
    int readBytes;         // number of total bytes read so far
    byte length1, length2; // each byte in an unsigned short int (see getLength())
    
    public int getLength() { // will be inaccurate if readBytes < 2
        return (int)(length1 << 8 | length2);
    }
    
    public void readData(SocketChannel c) {
        if (readBytes < 2) { // we don't yet know the length of the actual data
            ByteBuffer lengthBuffer = ByteBuffer.allocate(2 - readBytes);
            numBytesRead = c.read(lengthBuffer);
    
            if(readBytes == 0) {
                if(numBytesRead >= 1)
                    length1 = lengthBuffer.get();
    
                if(numBytesRead == 2)
                    length2 = lengthBuffer.get();
            } else if(readBytes == 1) {
                if(numBytesRead == 1)
                    length2 = lengthBuffer.get();
            }
            readBytes += numBytesRead;
        }
    
        if(readBytes >= 2) { // then we know we have the entire length variable
            // lazily-instantiate data buffers based on getLength()
            // read into data buffers, increment readBytes
    
            // (does not read more than the amount of this packet, so it does not
            // need to handle overflow into the next packet's data)
        }
    }
    
    public boolean complete() {
        return (readBytes > 2 && readBytes == getLength()+2);
    }
    

    基本上,我需要有关代码和整个过程的反馈。请提出任何改进建议。如果您对如何更好地实现整个系统有一些建议,即使对我的整个系统进行大修也可以。也欢迎书本推荐;我喜欢书。我只是觉得有些不对劲。

    这是我想出的通用解决方案,这要感谢Juliano的回答:(如果您有任何疑问,请随时发表评论)
    public void fillWriteBuffer() {
        while(!writePackets.isEmpty() && writeBuf.remaining() >= writePackets.peek().size()) {
            Packet p = writePackets.poll();
            assert p != null;
            p.writeTo(writeBuf);
        }
    }
    
    public void fillReadPackets() {
        do {
            if(readBuf.position() < 1+2) {
                // haven't yet received the length
                break;
            }
    
            short packetLength = readBuf.getShort(1);
    
            if(readBuf.limit() >= 1+2 + packetLength) {
                // we have a complete packet!
    
                readBuf.flip();
    
                byte packetType = readBuf.get();
    
                packetLength = readBuf.getShort();
    
                byte[] packetData = new byte[packetLength];
                readBuf.get(packetData);
    
                Packet p = new Packet(packetType, packetData);
                readPackets.add(p);
                readBuf.compact();
            } else {
                // not a complete packet
                break;
            }
    
        } while(true);
    }
    

    最佳答案

    可能这不是您要寻找的答案,但有人不得不说:您可能正在为一个非常简单的问题过度设计解决方案。

    在数据包完全到达之前,您就没有它们,甚至IncomingPacket也没有。您只有没有定义含义的字节流。通常,simple解决方案是将传入数据保留在缓冲区中(它可以是一个简单的byte []数组,但如果性能存在问题,则建议使用适当的弹性和圆形缓冲区)。每次读取后,您检查缓冲区的内容以查看是否可以从中提取整个数据包。如果可以,请构造Packet,从缓冲区的开头丢弃正确数量的字节,然后重复。如果无法提取整个数据包,则将这些传入的字节保留在那里,直到下一次成功从套接字读取内容为止。

    在进行此操作时,如果您正在通过流通道进行基于数据报的通信,我建议您在每个“数据包”的开头都包含一个魔术数字,以便可以测试连接的两端是否仍然同步。如果它们中的一个由于某种原因(错误)之一从流中读取或写入错误的字节数,则它们可能不同步。

    10-07 22:56