【问题标题】:Java TCP socket: data transfer is slowJava TCP 套接字:数据传输很慢
【发布时间】:2010-11-13 06:34:37
【问题描述】:

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

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

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

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

我错过了什么吗?

编辑:代码

这是我制作插座的地方

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()));

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

    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();
        }
    }

好的,这是我发送超过 ~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);
        }
    }

到目前为止,这似乎很简单......

【问题讨论】:

  • 请发布客户端和服务器的代码。
  • 是的,你肯定错过了一些东西。不确定是什么。您应该发布服务器和客户端代码的 sn-ps。
  • RECEIVE_BUFFER_SIZE和SEND_BUFFER_SIZE的值是多少?我强烈怀疑您实际上每 4 个字节发送一个数据包......
  • 在到达消息的逻辑结尾之前不要刷新任何内容。如果您在没有阅读响应的情况下进行流式传输,则根本不要刷新。并失去睡眠。这是没有意义的。

标签: java networking tcp sockets performance


【解决方案1】:

您确实不想在传输大量数据时写入单个字节。

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();
    }
}

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

【讨论】:

  • 太棒了——非常感谢。请参阅下面我对 ignasi35 的评论,了解在 DataxxxxStream 中包装套接字流可能出现的问题。我不知道为什么这么慢,特别是因为即使只使用 4 字节缓冲区来推送整数与 DataxxxxxStream.yyyyInt() 的传输时间也只有几秒钟,它应该(希望)在后面进行 4 字节缓冲场景,但显然正在做一些完全疯狂的事情
  • 您是在 localhost 上还是通过 Internet 传输了 1GiB?
  • 即使使用您的代码,在两台不同的机器(不是本地主机)之间传输数据时速度仍然很慢。
【解决方案2】:

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

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

切勿使用 DataInputStream/DataOutputStream 和套接字!!

如果我将套接字包装在 BufferedOutputStream/BufferedInputStream 中,生活就很棒。直接写就可以了。

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

对此的解释对我来说真的很有趣。但是在交换了所有东西之后,这就是问题所在。不信你自己试试。

感谢所有快速帮助。

【讨论】:

  • DataInputStream 不缓冲。如果您将 1 或其他非常少的字节数写入非缓冲流,则性能将下降。您还调用 get/setReceiveBufferSize。只有当您知道自己比 TCP 堆栈更聪明时,您才会这样做
  • DataXXXStreams 还调用默认的序列化方案,已知该方案非常慢。对于大额转账,您绝对应该避免使用它们。
  • 写入DataOutputStream 的速度与写入底层OutputStream 的速度*相同,减去one的开销> 方法调用。同样,从DataInputStream 读取的速度与从底层InputStream 读取的速度相同,但需要注意的是。 @markbernard DataXXXStreams 与“默认序列化方案”完全无关
  • @EJP 我的错。我在想 ObjectXXXStreams
【解决方案3】:

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

【讨论】:

  • 每次写入的这种小尺寸是问题的根源。由于链接相对较快,它会在每次写入整数后发送一个数据包。这个数据包将被包装在一个 IP 数据包中,并且会产生 TCP 开销。一次发送大约 1K 的数据包会很好地加快速度。
  • 没有“TCP 数据包大小”之类的东西。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

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

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

    【讨论】:

      【解决方案6】:

      由于我还不能对这个网站发表评论,我必须在这里给@Erik 写答案。

      问题在于 DataOutputStream 没有缓冲。 Java 中的整个 Stream-thing 都是基于装饰器设计模式。所以你可以写

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

      它将原始流包装在更高效的 BufferedOutputStream 中,然后将其包装到 DataOutputStream 中,该流提供额外的不错的功能,如 writeInt()、writeLong() 等。

      【讨论】:

        【解决方案7】:

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

        【讨论】:

        • 即使将套接字的输出流包装在 BufferedOutputStream 中也无济于事。我们来看这个:int --> DataOutputStream --> Socket 的输出流。一个 int(4 个字节)进入,变成单个字节(可能作为一个数组)并被推送到套接字。这非常慢。 0.6Gb 需要 23 分钟。现在我们做: int --> 4-byte array --> socket 的输出流 0.6Gb 在 ~3 秒内。 DataOutputStream 所做的与我在那里所做的有什么不同?有什么比将 int 转换为数组并将其提供给流更复杂的方法?
        • 哎呀,看起来 StackOverflow 吃换行符。这不是我想要的格式,但我希望你明白要点。
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

          要尝试的事情:

          • 发送数据时 CPU 是否处于 100%?如果是这样,请使用 visualvm 并进行 CPU 分析以查看时间花费在哪里
          • 使用 java.nio 中的 SocketChannel - 这些通常更快,因为它们可以更轻松地使用本机 IO - 当然,这仅在您的操作受 CPU 限制时才有帮助
          • 如果它不受 CPU 限制,则网络级别出现问题。使用数据包嗅探器对此进行分析。

          【讨论】:

            【解决方案10】:

            我正在使用 PrintWriter 发送数据。我删除了它并使用 BufferedOutputStream.send(String.getBytes()) 发送数据,发送速度提高了大约 10 倍。

            【讨论】:

              【解决方案11】:

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

              试试-Xmx1g

              【讨论】:

              • 我可以说,当人们在 cmets 部分中对合理的答案投反对票时,它真的让我很恼火。鉴于我有这个确切的问题,这是一个完全合理的建议!
              • 你一定有一些非常奇怪的代码。无需使用大量堆来传输大量数据。
              【解决方案12】:

              使用字节缓冲区发送数据

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-07-12
                • 1970-01-01
                • 1970-01-01
                • 2012-03-07
                • 1970-01-01
                • 2020-06-22
                • 2012-08-23
                • 2013-03-02
                相关资源
                最近更新 更多