О, как бы я хотел, чтобы TCP был основан на пакетах, как UDP! [см. комментарии] Но, увы, дело не в этом, поэтому я пытаюсь реализовать свой собственный пакетный уровень. Вот цепочка событий (игнорируя запись пакетов)
О, и мои Пакеты очень просто структурированы: два беззнаковых байта для длины, а затем байтовые [длина] данных. (Я не могу себе представить, если бы они были более сложными, я был бы до ушей в if
заявлениях!)
Server
находится в бесконечном цикле, принимая соединения и добавляя их в список Connection
с.
PacketGatherer
(другой поток) использует Selector
, чтобы выяснить, какие Connection.SocketChannel
s готовы к чтению.
- Он перебирает результаты и сообщает каждому
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);
}
В основном мне нужны отзывы о моем коде и общем процессе. Пожалуйста, предложите какие-либо улучшения. Даже капитальный ремонт всей моей системы будет в порядке, если у вас есть предложения о том, как лучше реализовать все это. Книжные рекомендации тоже приветствуются; Я люблю книги. Мне просто кажется, что что-то не так.
Вот общее решение, которое я нашел благодаря ответу Джулиано: (не стесняйтесь комментировать, если у вас есть какие-либо вопросы)
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);
}