哦,我希望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);
}
中的逻辑有点疯狂,能够读取长度的两个字节,然后读取实际数据。这是代码,快速简化&amp; amp;易读:
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);
}
基本上我需要有关我的代码和整个过程的反馈。请提出任何改进建议。如果你有关于如何更好地实现整个系统的建议,即使对整个系统进行检修也是可以的。也欢迎预订建议;我喜欢书。我只是觉得事情不太正确。
这是我想出的一般解决方案,感谢Juliano的回答:(如果您有任何问题,请随时发表评论)
{{1}}
答案 0 :(得分:7)
可能这不是您正在寻找的答案,但有人必须说出来:您可能会过度设计解决方案以解决一个非常简单的问题。
在完全到达之前,您没有数据包,甚至包括IncomingPacket
。您只有一个没有定义含义的字节流。通常,simple解决方案是将输入数据保存在缓冲区中(它可以是一个简单的byte []数组,但如果性能有问题,建议使用适当的弹性和循环缓冲区)。每次读取后,检查缓冲区的内容以查看是否可以从那里提取整个数据包。如果可以,则构造Packet
,从缓冲区的开头丢弃正确的字节数并重复。如果或者当您无法提取整个数据包时,请将这些传入的字节保留在那里,直到下次从套接字中成功读取内容为止。
当你在这里时,如果你正在通过流通道进行基于数据报的通信,我建议你在每个“数据包”的开头包含一个幻数,这样你就可以测试连接的两端仍然是同步的。如果出于某种原因(一个错误),它们之一可能会从流中读取或写入错误的字节数。
答案 1 :(得分:1)
难道你不能只读取任何准备读取的字节数,并将所有传入的字节输入数据包解析状态机吗?这意味着像任何其他传入数据流一样处理传入(TCP)数据流(通过串行线路,或USB,管道或任何其他......)
所以你会有一些Selector确定哪个连接有待读取的传入字节,以及有多少个。然后你会(对于每个连接)读取可用字节,然后将这些字节提供给(特定于连接)状态机实例(尽管可以从同一个类中读取和馈送)。然后,这个数据包解析状态机类会不时吐出已完成的数据包,然后将这些数据包交给任何将处理这些完整和解析数据包的人。
对于像
这样的数据包格式 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
来丢弃该字节。
答案 2 :(得分:1)
暂时忽略客户端断开连接和服务器关闭,这里或多或少是传统的套接字服务器结构:
就是这样。一切都可以在一个线程中。这里的关键是责任分离。 希望这会有所帮助。
答案 3 :(得分:0)
我认为你从一个稍微错误的方向接近这个问题。考虑数据结构,而不是考虑数据包。这就是你要发送的东西。实际上,是的,它是一个应用程序层数据包,但只是将其视为一个数据对象。然后,在最低级别,编写一个将读取线路的例程,并输出数据对象。这将为您提供我认为您正在寻找的抽象层。