Trying to pack TCP with non-blocking IO is hard! Am I doing something wrong?

Oh, how I wish TCP was packet based like UDP! [cm. comments] But, alas, this is not so, so I'm trying to implement my own package layer. Here's a chain of events (ignoring package writing)

Oh, and my packets are very simply structured: two unsigned bytes for length, and then byte [long] data. (I can’t imagine if they were more complex, I would have been in my ears in expressions if!)

  • Serveris in an infinite loop, taking connections and adding them to the Connections list .
  • PacketGatherer(another thread) uses Selectorto find out which ones Connection.SocketChannelare ready for reading.
  • He reviews the results and tells everyone Connection read().
  • Each Connectionhas a partial IncomingPacketand list Packetthat has been fully read and awaiting processing.
  • On read():
    • Tell partial IncomingPacketto read more data. ( IncomingPacket.readDatabelow)
    • If reading ( IncomingPacket.complete()) did this , make it Packetand paste it Packetinto a list that is awaiting processing, and then replace it with a new one IncomingPacket.

There are a couple of problems with this. First, only one packet is read at a time. If you IncomingPacketneed only one more byte, then only this byte is read. Of course, this can be fixed with a loop, but it is starting to get more complicated, and I wonder if there is a better way.

-, 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 data)
    }
}

public boolean complete() {
    return (readBytes > 2 && readBytes == getLength()+2);
}

. . , , . ; . , - .


, : ( , - )

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);
}
+3
4

, , , - : , , .

, , IncomingPacket s. . , , ( byte [], , ). , , . , Packet, . , , - .

, , "", , . , - () / .

+7

, , ? (TCP) , ( USB, - ...)

, , (-) . ( ) , ( ) ( ). , , , .

,

    2 magic header bytes to mark the start
    2 bytes of payload size (n)
    n bytes of payload data
    2 bytes of checksum

, ( , Java , )

wait_for_magic_byte_0,
wait_for_magic_byte_1,
wait_for_length_byte_0,
wait_for_length_byte_1,
wait_for_payload_byte (with a payload_offset variable counting),
wait_for_chksum_byte_0,
wait_for_chksum_byte_1

. , , wait_for_magic_byte_0.

+1

, :

  • , :
    • , Connection
    • , ( )
  • ( ), - :
    • ,
    • , , .
    • : , , ,
    • : , , ,
  • ( ), :
    • ,
    • (, - , ), ,

. . . , .

+1
source

I think you are approaching the problem from a slightly wrong direction. Instead of thinking about packages, think about a data structure. This is what you send. In fact, yes, this is an application-level package, but just think of it as a data object. Then, at the lowest level, write a procedure that will read the wire and output the data objects. This will give you the layer of abstraction that I think you are looking for.

0
source

Source: https://habr.com/ru/post/1740701/


All Articles