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

时间:2017-04-14 19:27:39

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

我正在使用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数据包。关于我如何进行调试的任何建议?

3 个答案:

答案 0 :(得分:3)

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

重点是:那些图书馆打算为你创建一个抽象。 TCP协议实际上是复杂;而且很可能;你不想处理所有微妙的细节。

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

示例:您的代码不处理异常,您只需让它们通过即可。你的代码不是关闭流;您的代码包含抑制警告注释。很显然:它是永远写的单元测试。

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

答案 1 :(得分:1)

你无法控制TCP数据包的方式!别想了。这是较低操作系统级别的所有部分。

直到您的邮件成功发送,您不必担心。

答案 2 :(得分:0)

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

您所遇到的是由于TCP的流导向特性(BTW,这在编写服务器和客户端时非常有用: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/