哦,我希望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
,从缓冲区的开头丢弃正确数量的字节,然后重复。如果无法提取整个数据包,则将这些传入的字节保留在那里,直到下一次成功从套接字读取内容为止。
在进行此操作时,如果您正在通过流通道进行基于数据报的通信,我建议您在每个“数据包”的开头都包含一个魔术数字,以便可以测试连接的两端是否仍然同步。如果它们中的一个由于某种原因(错误)之一从流中读取或写入错误的字节数,则它们可能不同步。