首页 文章

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

提问于
浏览
2

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

3 回答

  • 0

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

    关键是:那些库打算为你创建一个 abstraction . TCP协议实际上是 complex ;而且很可能;你不想处理所有微妙的细节 .

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

    示例:您的代码没有关闭流;您的代码包含抑制警告注释 . 显然:它是 never 用单元测试编写的 .

    这件事很重要 . 如果JVM发送一个或多个TCP包,则不行!

  • 3

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

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

  • 1

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

    您遇到的是由于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/ .

相关问题