简体   繁体   English

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

[英]Java TCP socket: data transfer is slow

I set up a server with a ServerSocket, connect to it with a client machine.我用 ServerSocket 设置了一个服务器,用客户端机器连接到它。 They're directly networked through a switch and the ping time is <1ms.它们通过交换机直接联网,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.传输 0.6Gb 需要 23 分钟。 I can push a much larger file in seconds via scp.我可以通过 scp 在几秒钟内推送一个更大的文件。

Any idea what I might be doing wrong?知道我可能做错了什么吗? I'm basically just looping and calling writeInt on the socket.我基本上只是在套接字上循环并调用 writeInt 。 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.速度问题与数据来自何处无关,即使我只是发送一个常量整数而不是从磁盘读取。

I tried setting the send and receive buffer on both sides to 4Mb, no dice.我尝试将两侧的发送和接收缓冲区设置为 4Mb,没有骰子。 I use a buffered stream for the reader and writer, no dice.我为读取器和写入器使用缓冲流,没有骰子。

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...到目前为止看起来很简单......

You do not want 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.这在我的机器上在短短 19 秒内复制了 1 GiB 数据。 The key here is using the InputStream.read and OutputStream.write methods that accept a byte array as parameter.这里的关键是使用接受字节数组作为参数的InputStream.readOutputStream.write方法。 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.缓冲区的大小并不重要,它应该比 5 大一点。 用上面的 BUFFER_SIZE 试验看看它如何影响速度,但也要记住它可能因您正在运行的每台机器而异这个程序上。 64 KiB seem to be a good compromise. 64 KiB 似乎是一个很好的折衷方案。

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.如果我将套接字包装在 BufferedOutputStream/BufferedInputStream 中,那么生活就会很棒。 Writing to it raw is just fine.写入原始数据就好了。

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.不过,感谢所有快速帮助。

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 数据包大小对齐以获得最佳性能。

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.我的猜测是您的客户端和服务器之间有一个 10 Mb/s 的网络,这就是您的传输速度缓慢的原因。 If this is the case, try using a DeflatoutOutputStream and an InflatorInputStream for your connection.如果是这种情况,请尝试为您的连接使用 DeflatoutOutputStream 和 InflatorInputStream。

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

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.看看能不能解决你的问题。

Since I cannot yet comment on this site, I must write answer to @Erik here.由于我还不能对这个网站发表评论,我必须在这里写下对@Erik 的回答。

The problem is that DataOutputStream doesn't buffer.问题是 DataOutputStream 没有缓冲。 The whole Stream-thing in Java is based on decorators design pattern. Java 中的整个 Stream 事物都基于装饰器设计模式。 So you could write所以你可以写

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));

It will wrap the original stream in a BufferedOutputStream which is more efficient, which is then wrapped into a DataOutputStream which offers additional nice features like writeInt(), writeLong() and so on.它将原始流包装在一个更有效的 BufferedOutputStream 中,然后将其包装到一个 DataOutputStream 中,该 DataOutputStream 提供额外的好功能,如 writeInt()、writeLong() 等。

@Erik: using DataXxxputStream is not the problem here. @Erik:使用 DataXxxputStream 不是这里的问题。 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. Bombe 的解决方案更好、更通用且更快。

You should download a good packet sniffer.您应该下载一个好的数据包嗅探器。 I'm a huge fan of WireShark personally and I end up using it every time I do some socket programming.我个人是WireShark 的忠实粉丝,每次我进行套接字编程时都会使用它。 Just keep in mind you've got to have the client and server running on different systems in order to pick up any packets.请记住,您必须让客户端和服务器在不同的系统上运行才能接收任何数据包。

Things to try:尝试的事情:

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

I was using PrintWriter to send data.我正在使用 PrintWriter 发送数据。 I removed that and sent data with BufferedOutputStream.send(String.getBytes()) and got about 10x faster sending.我删除了它并使用 BufferedOutputStream.send(String.getBytes()) 发送数据,并且发送速度提高了大约 10 倍。

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 JConsole I realized that the application was spending most of its time doing full GCs.我最近在大量数据的套接字传输方面遇到了类似的问题,仅通过查看JConsole我就意识到该应用程序大部分时间都在进行完整的 GC。

Try -Xmx1g试试-Xmx1g

使用字节缓冲区发送数据

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM