Java NIO 是如何拆分消息的?
How does Java NIO break up messages?
我正在编写一个玩具 Java NIO 服务器与普通 Java 客户端配对。客户端使用普通套接字向服务器发送字符串消息。服务器接收消息并将内容转储到终端。
我注意到来自客户端的相同消息每次都以不同的方式分解为字节缓冲区。我知道这是 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
作为编写器,它逐字符发送消息!所以每个角色都有一个数据包...
将编写器更改为 BufferedWriter
后,我的短消息现在按预期作为单个数据包发送。所以事实是 Java NIO 实际上做了一件聪明的事,将我的小数据包合并到 1 到 2 个字节缓冲区!
更新 2 - 澄清
谢谢大家的回复。谢谢@StephenC 指出,除非我自己对消息进行编码(是的,我在写入 BufferedWriter
后确实调用了 flush()
),否则我的消息总是有可能跨越多个数据包到达。
So the truth is Java NIO actually did the clever thing and merged my tiny
实际上,没有。合并发生在 BufferedWriter 层中。当应用程序刷新或关闭 DataOutputStream 或 BufferdWriters 缓冲区填满时,缓冲写入器只会向 NIO 层传送“一堆”字节。
我实际上指的是我第一次尝试使用 DataOutputStream
(我从一个在线示例中得到它,这显然是对 class 的不正确使用,既然你已经指出了它) . 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 套接字发送数据,则没有 "messages" 这样的套接字。您发送和接收的是字节流。
如果您询问是否可以发送 N 个字节的块,并让接收方在一次读取调用中恰好获得 N 个字节,那么答案是无法保证会发生。但是,TCP/IP 堆栈是 "breaking up" "messages"。不是蔚来。不是 Java.
通过 TCP/IP 连接发送的数据最终会分解为网络数据包进行传输。这通常会根据原始写入请求大小擦除任何 "message" 结构。
如果你想在 TCP/IP 字节流的顶部有一个可靠的消息结构,你需要在流本身中对其进行编码;例如使用 "end-of-message" 标记或在每条消息前加上字节数作为前缀。 (如果你想使用花哨的词,你需要在 TCP/IP 流的顶部实现一个 "message protocol"。)
关于你的更新,我觉得还有一些误区:
... it became apparent that the random "break up" was due to me using DataOutputStream for the writer, which sends the message character by character! So there was a packet for each character...
是的,对套接字流的大量小写入可能导致网络级别的严重碎片化。然而,它不会总是。如果由于网络带宽限制或接收器读取缓慢而有足够的 "back pressure",那么这将导致更大的数据包。
After changing the writer to BufferedWriter, my short message is now sent as a single packet, as expected.
是的。向堆栈添加缓冲是好的。但是,您可能正在做其他事情;例如在每条消息后调用 flush()
。如果您不这样做,那么我希望网络数据包包含一系列消息和部分消息。
此外,如果消息太大而无法放入单个网络数据包,或者如果存在严重的背压(见上文),那么您很可能会在一个数据包中收到多条/部分消息。无论哪种方式,接收方不应该 依赖于每次读取一条(完整)消息。
简而言之,您可能没有真正解决您的问题!!
So the truth is Java NIO actually did the clever thing and merged my tiny
实际上,没有。合并发生在 BufferedWriter
层。当应用程序刷新或关闭 DataOutputStream
或 BufferdWriter
s 缓冲区填满时,缓冲写入器将仅向 NIO 层传送 "bunch" 字节。
FWIW - 根据您对您正在做的事情的描述,使用 NIO 不太可能有助于提高性能。如果你想最大化性能,你应该停止使用 BufferedWriter
和 DataOutputStream
。相反,您的消息编码 "by hand",将字节或字符直接放入 ByteBuffer
或 CharBuffer
.
(另外 DataOutputStream
用于二进制数据,而不是文本。将一个放在 Writer
前面似乎不正确......如果那是你真正在做的。)
我正在编写一个玩具 Java NIO 服务器与普通 Java 客户端配对。客户端使用普通套接字向服务器发送字符串消息。服务器接收消息并将内容转储到终端。
我注意到来自客户端的相同消息每次都以不同的方式分解为字节缓冲区。我知道这是 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
作为编写器,它逐字符发送消息!所以每个角色都有一个数据包...
将编写器更改为 BufferedWriter
后,我的短消息现在按预期作为单个数据包发送。所以事实是 Java NIO 实际上做了一件聪明的事,将我的小数据包合并到 1 到 2 个字节缓冲区!
更新 2 - 澄清
谢谢大家的回复。谢谢@StephenC 指出,除非我自己对消息进行编码(是的,我在写入 BufferedWriter
后确实调用了 flush()
),否则我的消息总是有可能跨越多个数据包到达。
So the truth is Java NIO actually did the clever thing and merged my tiny
实际上,没有。合并发生在 BufferedWriter 层中。当应用程序刷新或关闭 DataOutputStream 或 BufferdWriters 缓冲区填满时,缓冲写入器只会向 NIO 层传送“一堆”字节。
我实际上指的是我第一次尝试使用 DataOutputStream
(我从一个在线示例中得到它,这显然是对 class 的不正确使用,既然你已经指出了它) . 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 套接字发送数据,则没有 "messages" 这样的套接字。您发送和接收的是字节流。
如果您询问是否可以发送 N 个字节的块,并让接收方在一次读取调用中恰好获得 N 个字节,那么答案是无法保证会发生。但是,TCP/IP 堆栈是 "breaking up" "messages"。不是蔚来。不是 Java.
通过 TCP/IP 连接发送的数据最终会分解为网络数据包进行传输。这通常会根据原始写入请求大小擦除任何 "message" 结构。
如果你想在 TCP/IP 字节流的顶部有一个可靠的消息结构,你需要在流本身中对其进行编码;例如使用 "end-of-message" 标记或在每条消息前加上字节数作为前缀。 (如果你想使用花哨的词,你需要在 TCP/IP 流的顶部实现一个 "message protocol"。)
关于你的更新,我觉得还有一些误区:
... it became apparent that the random "break up" was due to me using DataOutputStream for the writer, which sends the message character by character! So there was a packet for each character...
是的,对套接字流的大量小写入可能导致网络级别的严重碎片化。然而,它不会总是。如果由于网络带宽限制或接收器读取缓慢而有足够的 "back pressure",那么这将导致更大的数据包。
After changing the writer to BufferedWriter, my short message is now sent as a single packet, as expected.
是的。向堆栈添加缓冲是好的。但是,您可能正在做其他事情;例如在每条消息后调用 flush()
。如果您不这样做,那么我希望网络数据包包含一系列消息和部分消息。
此外,如果消息太大而无法放入单个网络数据包,或者如果存在严重的背压(见上文),那么您很可能会在一个数据包中收到多条/部分消息。无论哪种方式,接收方不应该 依赖于每次读取一条(完整)消息。
简而言之,您可能没有真正解决您的问题!!
So the truth is Java NIO actually did the clever thing and merged my tiny
实际上,没有。合并发生在 BufferedWriter
层。当应用程序刷新或关闭 DataOutputStream
或 BufferdWriter
s 缓冲区填满时,缓冲写入器将仅向 NIO 层传送 "bunch" 字节。
FWIW - 根据您对您正在做的事情的描述,使用 NIO 不太可能有助于提高性能。如果你想最大化性能,你应该停止使用 BufferedWriter
和 DataOutputStream
。相反,您的消息编码 "by hand",将字节或字符直接放入 ByteBuffer
或 CharBuffer
.
(另外 DataOutputStream
用于二进制数据,而不是文本。将一个放在 Writer
前面似乎不正确......如果那是你真正在做的。)