【问题标题】:Why does Java writeBytes send only one byte in first TCP packet and remaining in another TCP packet为什么Java writeBytes在第一个TCP数据包中只发送一个字节而在另一个TCP数据包中剩余
【发布时间】:2017-09-11 02:18:04
【问题描述】:

我正在使用this 链接开发一个简单的客户端服务器程序。我的服务器是 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 是一种流协议。除了最初的连接和连接的关闭之外,没有开始或结束。只有一个字节流。
  • 有人可以澄清投票背后的原因,因为我认为这是一个合理的问题吗?
  • 使用 write 而不是 writeBytes 的长度,然后调用 flush。

标签: java sockets tcp network-programming client-server


【解决方案1】:

您可以更改部分行为以获得更好的性能。实际上这是一种常见的做法。

您遇到的情况是由于 TCP 面向流的特性(顺便说一句,这在编写服务器和客户端时会很有用:How to read all of Inputstream in Server Socket JAVA)。当客户端写入套接字时,写入的数据实际上是写入发送缓冲区。 TCP 从发送缓冲区获取数据,它可以根据不同的变量(读取Are TCP/IP Sockets Atomic?)并将其发送到 TCP 段中的另一端。

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

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

clientSocket.setTcpNoDelay(true);

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

    【讨论】:

    • 谢谢。我正在尝试使用相同的服务器代码将数据发送到另一个不受我控制的客户端。在这种情况下,上面的代码只发送第一个 TCP 数据包。
    • 关闭该套接字时会发生什么?
    • 谢谢!这有帮助。
    • 这才是最重要的。是的,我可能“误读”并且没有首先看到您的真正问题。真的很高兴我们能解决这个问题。尤其是今天你的接受让我超过了每日限制!
    • 考虑提供允许对数据包进行这种细粒度控制的库。知道标准库无法实现这一点绝对有用,但事实上,您的答案实际上并不能帮助需要这种控制的人。
    【解决方案3】:

    您无法控制 TCP 数据包的方式!别再想了。这都是较低操作系统级别的东西的一部分。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-13
      • 2010-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多