Java NIO如何分解消息?
我正在写一个玩具Java NIO服务器与普通的Java客户端配对。 客户端使用普通的Socket向服务器发送字符串消息。 服务器收到消息并将内容转储给终端。
我注意到,客户端发出的相同消息每次都以不同的方式分解为不同的字节缓冲区。 我知道这是NIO的预期行为,但想大致了解NIO如何决定切断信息?
示例:向服务器发送字符串“这是测试消息”。 以下是服务器日志摘录(每行代表1个接收的字节缓冲区)。
Run 1:
Server receiving: this is a test message
Run 2:
Server receiving: t
Server receiving: his is a test message
Run 3:
Server receiving: this is
Server receiving: a test message
更新 - 已解决的问题
我已经安装了Wireshark来分析这些数据包,并且很明显,随机“分手”是由于我使用DataOutputStream
作为DataOutputStream
器的,它会DataOutputStream
发送消息! 所以每个角色都有一个包
将BufferedWriter
更改为BufferedWriter
,我的短消息按照预期作为单个数据包发送。 所以事实是,Java NIO实际上做了巧妙的事情,并将我的小包合并为1到2个字节缓冲区!
UPDATE2 - 澄清
谢谢大家的回复。 谢谢@StephenC指出,除非我自己对消息进行编码(是的,在写入BufferedWriter
之后我确实调用了flush()
),我的消息总是有可能跨越多个数据包到达。
所以事实是Java NIO实际上做了巧妙的事情,并合并了我的小事
其实,没有。 合并发生在BufferedWriter图层中。 当应用程序刷新或关闭DataOutputStream或BufferdWriters缓冲区填满时,缓冲写入程序将只向NIO层提供“一堆”字节。
实际上,我指的是我第一次尝试使用DataOutputStream
(我从网上的一个例子中得到了这个例子,显然这是现在你指出的类的不正确使用)。 BufferedWriter
没有参与。 那个简单的作家就是这样
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
out.writeBytes("this is a test message");
Wireshark确认此消息已发送(本地主机上的服务器)1个字符的数据包(总共22个数据包,实际消息不包括所有ACK等)。
我可能是错的,但是这种行为似乎表明NIO服务器将这22个数据包组合成1-2个字节缓冲区?
我试图在这里实现的最终游戏是一个简单的Java NIO服务器,它能够使用来自各种客户端的TCP接收请求和数据流,有些可能由第三方以C ++或C#编写。 这不是时间紧迫的,因此客户可以一次发送所有数据,服务器可以按照自己的步调处理它们。 这就是为什么我用Java编写了一个使用普通Socket
而不是NIO客户端的玩具客户端。 因此,在这种情况下,客户端不能直接操作ByteBuffer
,所以我可能需要某种消息格式。 我可以做这个工作吗?
如果您通过TCP / IP套接字发送数据,则不会有“消息”。 你发送和接收的是一串字节。
如果您问是否可以发送N个字节的块,并且让接收器在一次读取调用中得到完全N个字节,那么答案是不能保证会发生。 但是,TCP / IP堆栈正在“分解”“消息”。 不是NIO。 不是Java。
通过TCP / IP连接发送的数据最终被分解为网络数据包进行传输。 这通常会根据原始写请求大小擦除任何“消息”结构。
如果你想在TCP / IP字节流的顶部有一个可靠的消息结构,你需要将它编码到流本身; 例如使用“消息结束”标记或在每个消息前加上一个字节计数。 (如果您想使用花哨的单词,则需要在TCP / IP流的顶部实施“消息协议”。)
关于你的更新,我认为还有一些误解:
......显而易见的是,随机的“分手”是由于我使用DataOutputStream作为作者,因为它会逐字地发送消息! 所以每个角色都有一个包
是的,对套接字流的很多小写操作可能会导致网络级别的严重碎片化。 但是,它并不总是。 如果由于网络带宽限制或接收器读数缓慢而导致有足够的“背压”,那么这将导致更大的数据包。
将编写器更改为BufferedWriter之后,我的短消息按照预期作为单个数据包发送。
是。 向堆栈添加缓冲区是很好的。 但是,你可能正在做其他事情; 例如在每条消息之后调用flush()
。 如果你没有,那么我会期望一个网络数据包包含一系列消息和部分消息。
更重要的是,如果消息太大而无法放入单个网络数据包,或者存在严重的背压(见上文),那么无论如何,您都有可能在数据包中收到多个/部分消息。 无论哪种方式,接收者在每次读取时都不应该依赖获取一条(全部)消息。
总之,你可能没有真正解决你的问题!
所以事实是Java NIO实际上做了巧妙的事情,并合并了我的小事
其实,没有。 合并发生在BufferedWriter
图层中。 当应用程序刷新或关闭DataOutputStream
或BufferdWriter
的缓冲区填满时,缓冲写入程序将只向NIO层发送“一堆”字节。
FWIW - 给出你对自己所做事情的描述,但不太可能使用NIO来帮助提升性能。 如果你想最大化性能,你应该停止使用BufferedWriter
和DataOutputStream
。 相反,你的消息编码“手工”,将字节或字符直接放入ByteBuffer
或CharBuffer
。
(另外DataOutputStream
是用于二进制数据,而不是文本。把一个放在Writer
前面看起来不太合适......如果这是你真正做的。)