为什么 Java writeBytes 在第一个 TCP 数据包中只发送一个字节并保留在另一个 TCP 数据包中

Why does Java writeBytes send only one byte in first TCP packet and remaining in another TCP packet

我正在使用 this link 开发一个简单的客户端服务器程序。我的服务器是 Mac 机器,客户端是 Windows 机器 (Win 10)。

客户端-服务器通信正常。当我检查使用 Wireshark 发送的字节时,在第一个 TCP 数据包中只发送了一个字符。还有另一个 TCP 数据包,其余数据将在其中发送。

即如果我发送 "qwerty",客户端发送 "q",服务器响应,然后客户端发送 "werty"。同样服务器发送"Q",客户端响应然后服务器发送"WERTY".

这是我的客户端服务器代码。我在执行 writeBytes() 后刷新数据。

如何强制在单个 TCP 数据包中发送数据?

客户代码:

import java.io.*;
import java.net.*;

class Client
{
    public static void main(String argv[]) throws Exception
    {
        String sentence;
        String modifiedSentence;
        BufferedReader inFromUser = new BufferedReader( new InputStreamReader(System.in));
        Socket clientSocket = new Socket("192.1.162.65", 6789);
        DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
        BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        System.out.println("Enter some text: " + inFromServer);
        sentence = inFromUser.readLine();
        outToServer.writeBytes(sentence + '\n');
        outToServer.flush();
        modifiedSentence = inFromServer.readLine();
        System.out.println("FROM SERVER: " + modifiedSentence);
        clientSocket.close();
    }
}

服务器代码:

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void createListener(ServerSocket serverSocket) throws Exception {

        String clientSentence;
        String capitalizedSentence;
        @SuppressWarnings("resource")
        ServerSocket welcomeSocket = new ServerSocket(6789);

        while (true) {
            @SuppressWarnings("resource")
            Socket connectionSocket = welcomeSocket.accept();
            BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
            DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
            clientSentence = inFromClient.readLine();
            System.out.println("Received: " + clientSentence);
            capitalizedSentence = clientSentence.toUpperCase() + '\n';
            outToClient.writeBytes(capitalizedSentence);
            outToClient.flush();
        }
    }

    public static void main(String[] args) throws Exception {
        Server.createListener(null);
    }
}

编辑 1: 这个问题背后的原因是,我试图使用相同的服务器代码将数据发送到另一个不受我控制的客户端。在那种情况下,上面的代码只发送第一个 TCP 数据包。我没有在 Wireshark 上看到第二个 TCP 数据包。关于如何调试它有什么建议吗?

您无法控制 TCP 打包数据的方式!别想了。这都是较低 OS 级别的内容。

在您的消息发送成功之前,您不必担心。

据我所知,你不能,更重要的是:你应该

重点是:那些库打算为您创建一个抽象。 TCP协议其实复杂;并且最有可能;您不想处理所有细微的细节。

所以这里没有答案:除非你遇到这个实现的真正问题(比如不可接受的性能损失)——你应该考虑编写清晰、可读的代码;而不是摆弄 TCP 堆栈实现细节!

示例:您的代码不处理异常,您只是让它们通过。您的代码没有关闭流;您的代码包含抑制警告注释。显然:从未在编写时考虑到单元测试。

这些事情很重要。如果 JVM 发送一个或两个 TCP 包,则不会!

您可以更改部分行为以获得更好的性能。其实这是一个普遍的做法。

您遇到的问题是由于 TCP 的面向流的性质(顺便说一句,这在编写您的服务器和客户端时会很有用:How to read all of Inputstream in Server Socket JAVA). When the client writes to the socket, the data written is actually written to a send buffer. TCP gets the data from the send buffer, what it can depending on different variables (read )并将它发送到另一端一个 TCP 段。

在你的情况下,TCP 获取第一个字符,很可能是因为它获取数据的速度比数据写入发送缓冲区的速度快,并且发送一个字节的 TCP 段。当 ACK 从另一端返回时,TCP 将发送带有其余字节的第二段(实际上,例如,它可以在第二条消息中仅发送两个字节,然后发送其余字节)。

您无法改变第一条消息只发送第一个字符的事实,实际上这应该是随机的,有时它可以发送多个字符。但是,您可以更改 TCP 在发送消息的其余部分之前等待 ACK 的部分。此行为主要是由于 Nagle 算法,您可以更改它:

clientSocket.setTcpNoDelay(true);

在此之后您应该会看到第二条消息不等待 ACK。在您的测试中,您可能看不出有什么不同,但如果您的客户端位于美国东海岸,而服务器位于西海岸,则第二条消息可能会延迟 20-40 毫秒。如果客户端在中国或印度或南美,延迟甚至可能为 300 毫秒。

其他有助于理解的因素是初始拥塞 window 尽管我认为它不会影响您的测试。您可以在此处阅读有关 initcwnd 的更多信息:https://www.cdnplanet.com/blog/tune-tcp-initcwnd-for-optimum-performance/.