Java TCP 套接字:数据传输很慢

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1169739/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 00:30:00  来源:igfitidea点击:

Java TCP socket: data transfer is slow

javanetworkingtcpsocketsperformance

提问by

I set up a server with a ServerSocket, connect to it with a client machine. They're directly networked through a switch and the ping time is <1ms.

我用 ServerSocket 设置了一个服务器,用客户端机器连接到它。它们通过交换机直接联网,ping 时间<1ms。

Now, I try to push a "lot" of data from the client to the server through the socket's output stream. It takes 23 minutes to transfer 0.6Gb. I can push a much larger file in seconds via scp.

现在,我尝试通过套接字的输出流将大量数据从客户端推送到服务器。传输 0.6Gb 需要 23 分钟。我可以通过 scp 在几秒钟内推送一个更大的文件。

Any idea what I might be doing wrong? I'm basically just looping and calling writeInt on the socket. The speed issue doesn't matter where the data is coming from, even if I'm just sending a constant integer and not reading from disk.

知道我可能做错了什么吗?我基本上只是在套接字上循环并调用 writeInt 。速度问题与数据来自何处无关,即使我只是发送一个常量整数而不是从磁盘读取。

I tried setting the send and receive buffer on both sides to 4Mb, no dice. I use a buffered stream for the reader and writer, no dice.

我尝试将两侧的发送和接收缓冲区设置为 4Mb,没有骰子。我为读取器和写入器使用缓冲流,没有骰子。

Am I missing something?

我错过了什么吗?

EDIT: code

编辑:代码

Here's where I make the socket

这是我制作插座的地方

System.out.println("Connecting to " + hostname);

    serverAddr = InetAddress.getByName(hostname);

    // connect and wait for port assignment
    Socket initialSock = new Socket();
    initialSock.connect(new InetSocketAddress(serverAddr, LDAMaster.LDA_MASTER_PORT));
    int newPort = LDAHelper.readConnectionForwardPacket(new DataInputStream(initialSock.getInputStream()));
    initialSock.close();
    initialSock = null;

    System.out.println("Forwarded to " + newPort);

    // got my new port, connect to it
    sock = new Socket();
    sock.setReceiveBufferSize(RECEIVE_BUFFER_SIZE);
    sock.setSendBufferSize(SEND_BUFFER_SIZE);
    sock.connect(new InetSocketAddress(serverAddr, newPort));

    System.out.println("Connected to " + hostname + ":" + newPort + " with buffers snd=" + sock.getSendBufferSize() + " rcv=" + sock.getReceiveBufferSize());

    // get the MD5s
    try {
        byte[] dataMd5 = LDAHelper.md5File(dataFile),
               indexMd5 = LDAHelper.md5File(indexFile);

        long freeSpace = 90210; // ** TODO: actually set this **

        output = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream()));
        input  = new DataInputStream(new BufferedInputStream(sock.getInputStream()));

Here's where I do the server-side connection:

这是我进行服务器端连接的地方:

    ServerSocket servSock = new ServerSocket();
    servSock.setSoTimeout(SO_TIMEOUT);
    servSock.setReuseAddress(true);
    servSock.bind(new InetSocketAddress(LDA_MASTER_PORT));

    int currPort = LDA_START_PORT;

    while (true) {
        try {
            Socket conn = servSock.accept();
            System.out.println("Got a connection.  Sending them to port " + currPort);
            clients.add(new MasterClientCommunicator(this, currPort));
            clients.get(clients.size()-1).start();

            Thread.sleep(500);

            LDAHelper.sendConnectionForwardPacket(new DataOutputStream(conn.getOutputStream()), currPort);

            currPort++;
        } catch (SocketTimeoutException e) {
            System.out.println("Done listening.  Dispatching instructions.");
            break;
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

Alright, here's where I'm shipping over ~0.6Gb of data.

好的,这里是我要发送超过 0.6Gb 数据的地方。

public static void sendTermDeltaPacket(DataOutputStream out, TIntIntHashMap[] termDelta) throws IOException {
    long bytesTransferred = 0, numZeros = 0;

    long start = System.currentTimeMillis();

    out.write(PACKET_TERM_DELTA); // header     
    out.flush();
    for (int z=0; z < termDelta.length; z++) {
        out.writeInt(termDelta[z].size()); // # of elements for each term
        bytesTransferred += 4;
    }

    for (int z=0; z < termDelta.length; z++) {
        for (int i=0; i < termDelta[z].size(); i++) {
            out.writeInt(1);
            out.writeInt(1);
        }
    }

It seems pretty straightforward so far...

到目前为止看起来很简单......

回答by Spencer Ruport

You should download a good packet sniffer. I'm a huge fan of WireSharkpersonally and I end up using it every time I do some socket programming. Just keep in mind you've got to have the client and server running on different systems in order to pick up any packets.

您应该下载一个好的数据包嗅探器。我个人是WireShark 的忠实粉丝,每次我进行套接字编程时都会使用它。请记住,您必须让客户端和服务器在不同的系统上运行才能接收任何数据包。

回答by Midhat

Maybe you should try sending ur data in chunks(frames) instead of writing each byte seperately. And align your frames with the TCP packet size for best performance.

也许您应该尝试以块(帧)的形式发送您的数据,而不是单独写入每个字节。并将您的帧与 TCP 数据包大小对齐以获得最佳性能。

回答by Peter Lawrey

Can you try doing this over loopback, it should then transfer the data in second.

您可以尝试通过环回执行此操作,然后它应该在第二次传输数据。

If it takes minutes, there is something wrong with your application. If is only slow sending data over the internet it could be you network link which is slow.

如果需要几分钟,则您的应用程序有问题。如果只是通过互联网发送数据很慢,那可能是你的网络链接很慢。

My guess is that you have a 10 Mb/s network between your client and your server and this is why your transfer is going slowly. If this is the case, try using a DeflatoutOutputStream and an InflatorInputStream for your connection.

我的猜测是您的客户端和服务器之间有一个 10 Mb/s 的网络,这就是您的传输速度缓慢的原因。如果是这种情况,请尝试为您的连接使用 DeflatoutOutputStream 和 InflatorInputStream。

回答by futureelite7

How are you implementing the receiving end? Please post your receiving code as well.

你是如何实现接收端的?请同时发布您的接收代码。

Since TCP is a reliable protocol, it will take steps to make sure the client is able to receive all of the data sent by the sender. This means that if your client cannot get the data out of the data receive buffer in time, then the sending side will simply stop sending more data until the client has a chance to read all the bytes in the receiving buffer.

由于 TCP 是一种可靠的协议,它将采取措施确保客户端能够接收发送方发送的所有数据。这意味着如果您的客户端无法及时从数据接收缓冲区中获取数据,那么发送方将简单地停止发送更多数据,直到客户端有机会读取接收缓冲区中的所有字节。

If your receiving side is reading data one byte at a time, then your sender probably will spend a lot of time waiting for the receiving buffer to clear, hence the long transfer times. I'll suggest changing your receiving code to reading as many bytes as possible in each read operation. See if that will solve your problem.

如果您的接收方一次读取一个字节的数据,那么您的发送方可能会花费大量时间等待接收缓冲区清除,因此传输时间很长。我建议将您的接收代码更改为在每次读取操作中读取尽可能多的字节。看看这是否能解决你的问题。

回答by oxbow_lakes

How is your heap size set? I had a similar problem recently with the socket transfer of large amounts of data and just by looking at JConsoleI realized that the application was spending most of its time doing full GCs.

你的堆大小是如何设置的?我最近在大量数据的套接字传输方面遇到了类似的问题,只是通过查看JConsole我意识到应用程序大部分时间都在进行完整的 GC。

Try -Xmx1g

尝试 -Xmx1g

回答by Michael Borgwardt

Things to try:

尝试的事情:

  • Is the CPU at 100% while the data is being sent? If so, use visualvm and do a CPU profiling to see where the time is spent
  • Use a SocketChannel from java.nio - these are generally faster since they can use native IO more easily - of course this only helps if your operation is CPU bound
  • If it's not CPU bound, there's something going wrong at the network level. Use a packet sniffer to analyze this.
  • 发送数据时 CPU 是否为 100%?如果是这样,请使用visualvm并进行CPU分析以查看时间花在何处
  • 使用 java.nio 中的 SocketChannel - 这些通常更快,因为它们可以更轻松地使用本机 IO - 当然这仅在您的操作受 CPU 限制时才有帮助
  • 如果它不受 CPU 限制,则网络级别出现问题。使用数据包嗅探器对此进行分析。

回答by Michael Borgwardt

Hey, I figured I'd follow up for anyone that was interested.

嘿,我想我会跟进任何感兴趣的人。

Here's the bizarre moral of the story:

这是这个故事的奇异寓意:

NEVER USE DataInputStream/DataOutputStream and sockets!!

永远不要使用 DataInputStream/DataOutputStream 和套接字!!

If I wrap the socket in a BufferedOutputStream/BufferedInputStream, life is great. Writing to it raw is just fine.

如果我将套接字包装在 BufferedOutputStream/BufferedInputStream 中,那么生活就会很棒。写入原始数据就好了。

But wrap the socket in a DataInputStream/DataOutputStream, or even have DataOutputStream(BufferedOutputStream(sock.getOutputStream())) is EXTREMELY SLOW.

但是将套接字包装在 DataInputStream/DataOutputStream 中,或者甚至让 DataOutputStream(BufferedOutputStream(sock.getOutputStream())) 非常慢。

An explanation for that would be really interesting to me. But after swapping everything in and out, this is what's up. Try it yourself if you don't believe me.

对此的解释对我来说真的很有趣。但是在把所有东西都换进换出之后,就是这样。如果你不相信我,你自己试试。

Thanks for all the quick help, though.

不过,感谢所有快速帮助。

回答by Bombe

You do notwant to write single bytes when you are transferring large amounts of data.

希望在您传输大量数据写入单个字节。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Transfer {

    public static void main(String[] args) {
        final String largeFile = "/home/dr/test.dat"; // REPLACE
        final int BUFFER_SIZE = 65536;
        new Thread(new Runnable() {
            public void run() {
                try {
                    ServerSocket serverSocket = new ServerSocket(12345);
                    Socket clientSocket = serverSocket.accept();
                    long startTime = System.currentTimeMillis();
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int read;
                    int totalRead = 0;
                    InputStream clientInputStream = clientSocket.getInputStream();
                    while ((read = clientInputStream.read(buffer)) != -1) {
                        totalRead += read;
                    }
                    long endTime = System.currentTimeMillis();
                    System.out.println(totalRead + " bytes read in " + (endTime - startTime) + " ms.");
                } catch (IOException e) {
                }
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                    Socket socket = new Socket("localhost", 12345);
                    FileInputStream fileInputStream = new FileInputStream(largeFile);
                    OutputStream socketOutputStream = socket.getOutputStream();
                    long startTime = System.currentTimeMillis();
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int read;
                    int readTotal = 0;
                    while ((read = fileInputStream.read(buffer)) != -1) {
                        socketOutputStream.write(buffer, 0, read);
                        readTotal += read;
                    }
                    socketOutputStream.close();
                    fileInputStream.close();
                    socket.close();
                    long endTime = System.currentTimeMillis();
                    System.out.println(readTotal + " bytes written in " + (endTime - startTime) + " ms.");
                } catch (Exception e) {
                }
            }
        }).start();
    }
}

This copies 1 GiB of data in short over 19 seconds on my machine. The key here is using the InputStream.readand OutputStream.writemethods that accept a byte array as parameter. The size of the buffer is not really important, it just should be a bit larger than, say, 5. Experiment with BUFFER_SIZE above to see how it effects the speed but also keep in mind that it probably is different for every machine you are running this program on. 64 KiB seem to be a good compromise.

这在我的机器上在短短 19 秒内复制了 1 GiB 数据。这里的关键是使用接受字节数组作为参数的InputStream.readOutputStream.write方法。缓冲区的大小并不重要,它应该比 5 大一点。 用上面的 BUFFER_SIZE 试验看看它如何影响速度,但也要记住它可能因您正在运行的每台机器而异这个程序上。64 KiB 似乎是一个很好的折衷方案。

回答by ignasi35

@Erik: using DataXxxputStream is not the problem here. Problem is you were sending data in too small chunks. Using a buffer solved your problem because even you would write bit by bit the buffer would solve the problem. Bombe's solution is much nicer, generic and faster.

@Erik:使用 DataXxxputStream 不是这里的问题。问题是您发送的数据块太小。使用缓冲区解决了您的问题,因为即使您一点一点地写入缓冲区也可以解决问题。Bombe 的解决方案更好、更通用且更快。

回答by Nasif

USe Byte buffer for sending the data

使用字节缓冲区发送数据