【问题标题】:Tweaking performance of Java's sockets调整 Java 套接字的性能
【发布时间】:2012-05-23 19:18:34
【问题描述】:

我创建了一个远程桌面控制应用程序。显然,它由客户端和服务器部分组成:

服务器:

  • 从客户端接收鼠标/键盘操作;
  • 将桌面截图发送给客户端。

客户:

  • 从服务器接收截图;
  • 发送鼠标/键盘动作;

考虑发送屏幕截图。当我将家用 PC 用作服务器时,我最终会得到 1920x1080 的屏幕截图尺寸。通过使用JAI Image I/O Tools 并将其编码为PNG,我能够为如此大的图像实现以下统计数据:

  1. 写入时间 ~0.2 秒; (不是进入套接字,而是进入一些“常规”输出流,即编码时间
  2. 读取时间 ~0.05 s; (不是来自套接字,而是来自一些“常规”输入流,即解码时间
  3. 大小约为 250 KB;
  4. 完美的品质。

因此,根据 #1 - 理想的可能 FPS 应该是 ~5。

不幸的是,我什至无法达到 5 FPS,甚至 2 FPS。我搜索了瓶颈,发现向/从 socket I/O 流 写入/读取需要大约 2 秒(请参阅附录 1 和 2 以了解说明)。这当然是不可接受的。

我对该主题进行了一些研究 - 并在两侧添加了套接字 I/O 流的缓冲(使用 BufferedInputStreamBufferedOutputStream)。我从 64 KB 大小开始。这确实提高了性能。但仍然不能有至少 2 FPS!另外,我用Socket#setReceiveBufferSizeSocket#setSendBufferSize进行了实验,速度有一些变化,但我不知道它们的具体表现如何,因此我不知道该使用哪些值。

看初始化代码:

服务器:

    ServerSocket serverSocket = new ServerSocket();
    serverSocket.setReceiveBufferSize( ? ); // #1
    serverSocket.bind(new InetSocketAddress(...));

    Socket clientSocket = serverSocket.accept();
    clientSocket.setSendBufferSize( ? ); // #2
    clientSocket.setReceiveBufferSize( ? ); // #3

    OutputStream outputStream = new BufferedOutputStream(
            clientSocket.getOutputStream(), ? ); // #4
    InputStream inputStream = new BufferedInputStream(
            clientSocket.getInputStream(), ? ); // #5

客户:

    Socket socket = new Socket(...);
    socket.setSendBufferSize( ? ); // #6
    socket.setReceiveBufferSize( ? ); // #7

    OutputStream outputStream = new BufferedOutputStream(
            socket.getOutputStream(), ? ); // #8
    InputStream inputStream = new BufferedInputStream(
            socket.getInputStream(), ? ); // #9

问题:

  1. 您会为所有人推荐哪些值(以提高性能) 这些案例,为什么?
  2. 请澄清Socket#setReceiveBufferSizeSocket#setSendBufferSize 行为。
  3. 您可以建议哪些其他方法/技术来提高性能 这样的应用程序?
  4. Skype 提供高质量的实时桌面传输 - 他们是如何做到的?

附录1:添加客户端套接字读取的展开伪代码(@mcfinnigan):

while(true) {
    // objectInputStream is wrapping socket's buffered input stream.
    Object object = objectInputStream.readObject(); // <--- Bottleneck (without setting proper buffer sizes is ~2 s)

    if(object == null)
        continue;

    if(object.getClass() == ImageCapsule.class) {
        ImageCapsule imageCapsule = (ImageCapsule)object;

        screen = imageCapsule.read(); // <--- Decode PNG (~0.05 s)

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                repaint();
            }
        });
    }
}

附录2:添加服务器套接字写入的展开伪代码(@EJP):

while(true) {
    // objectOutputStream is wrapping socket's buffered output stream.
    BufferedImage screen = ... // obtaining screenshot
    ImageCapsule imageCapsule = new ImageCapsule();

    imageCapsule.write(screen, formatName()); // <--- Encode PNG (~0.2 s)

    try {
        objectOutputStream.writeObject(imageCapsule); // <--- Bottleneck (without setting proper buffer sizes is ~2 s)
    }
    finally {
        objectOutputStream.flush();
        objectOutputStream.reset(); // Reset to free written objects.
    }
}

结论:

感谢您的回答,特别是 EJP - 他让我明白了一些事情。如果您像我一样 - 寻求有关如何调整套接字性能的答案,您绝对应该检查 TCP/IP Sockets in Java, Second Edition: Practical Guide for Programmers,尤其是第 6 章“幕后”,它描述了 *Socket 类的幕后发生的事情,发送和接收缓冲区是如何管理和利用(这是性能的主要关键)。

【问题讨论】:

  • 您确定套接字设置是您的限制因素吗?您的客户端和服务器之间有什么样的连接?您的服务器的上行速度是多少?
  • 你能把你从客户端套接字读取的代码贴出来吗?
  • @Roger Lindsjö 你所说的“什么样的连接”是什么意思?如何测量我的服务器的速度?
  • 为什么要测试 null?并继续?您是否希望将空值写入ObjectOutputStream?

标签: java performance sockets networking


【解决方案1】:
  • 写入时间 ~0.2 秒;
  • 读取时间 ~0.05 秒;

如果不考虑中间网络的延迟和带宽,这些目标完全没有意义。

大小约 250 KB;

题外话。图片大小由你决定,与实际编程无关,这是本站的宗旨。

完美的品质。

“完美质量”只要求您不丢弃任何位,而您无论如何都不会通过 TCP 获得这些位。

  serverSocket.setReceiveBufferSize( ? ); // #1

设置所有接受的套接字的接收缓冲区大小。将其设置为尽可能大,如果可能的话,超过 64k。

socket.setSendBufferSize( ? ); // #6

将其设置为尽可能大,如果可能的话,超过 64k。

    socket.setReceiveBufferSize( ? ); // #7

由于这是一个接受的套接字,您已经在上面完成了此操作。删除。

    OutputStream outputStream = new BufferedOutputStream(
            socket.getOutputStream(), ? ); // #8
    InputStream inputStream = new BufferedInputStream(
            socket.getInputStream(), ? ); // #9

这些的默认值为 8k;只要你有合适的套接字缓冲区大小就足够了。

对于所有这些情况,您会推荐哪些值(以提高性能)以及为什么?

见上文。

请澄清Socket#setReceiveBufferSize()Socket#setSendBufferSize() 的行为。

它们控制 TCP“窗口”的大小。这是一个相当深奥的话题,但其想法是使大小至少等于网络的带宽延迟乘积,即以字节为单位的带宽乘以以秒为单位的延迟 >= 以字节为单位的缓冲区大小。

您可以建议哪些其他方法/技术来提高此类应用程序的性能?

不要在发送数据时忙于睡觉和做其他任务。在您可以安排的最紧凑的循环中尽可能快地发送它。

Skype 提供高质量的实时桌面传输 - 他们是如何做到的?

题外话,可能不可知,除非 Skype 员工碰巧想在这里泄露公司机密。

【讨论】:

  • 感谢您提供此信息。但是,关于图像写入/读取时间,我必须不同意您的看法。查看原始帖子 - 我在那里添加了说明。这些不是对套接字本身的写入/读取,而是对“常规” I/O 流的写入/读取。因此,这个时间显示了编码/解码 PNG 所需的平均时间。想一想——无论连接速度如何,我都无法以每 ~0.2 秒的速度发送 PNG;无论连接速度如何,我都无法以每 ~0.05 秒的速度读取 PNG;
  • @Haroogan 我的观点是,你不能比网络更快地读写,所以为每个人指定时间是徒劳的。
  • 图像的大小也是如此——我觉得这对于考虑缓冲区的大小很重要,不是吗?
  • @Haroogan 不,为什么?唯一重要的是 TCP 窗口大小,它由接收方的接收缓冲区大小决定。
  • 实际上恰恰相反——你可以调整网络——让它更快。但在一般情况下 - 您对 PNG 的编码/解码时间无能为力。因此,在这种情况下,PNG 的编码/解码(写入/读取)时间决定了最短传输时间,而网络有一个额外的(次要)影响,我目前正在尝试减轻这种影响。
【解决方案2】:

问题很明确。 我上面的人建议使用多个 nio 通道,我宁愿使用多个阻塞套接字(nio 并不快)。但是,这并没有改善网络通信,这是并行编程,在您的情况下绝对是一个很好的解决方案。 让我提出以下建议

http://docs.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html#setTcpNoDelay(boolean)

将其设置为 true,您将能够以增加带宽消耗为代价来加快套接字通信速度。

【讨论】:

    【解决方案3】:

    您可以查看 Java NIO (http://www.cs.brown.edu/courses/cs161/papers/j-nio-ltr.pdf),使用多个通道等。

    【讨论】:

      【解决方案4】:
      • 如果客户端为每个图像打开新的 TCP 连接,那么TCP slow start 可能会导致速度变慢。 --> 对所有帧使用相同的 TCP 连接。

      • TCP 有自己的缓冲区,这些缓冲区是 TCP 使用的最佳方式 --> BufferedOutputStream 有时会带来好处,有时不会。

      • 通常只有一小部分屏幕在屏幕截图之间发生变化。 --> 只传输改变的部分。

      【讨论】:

      • 我知道#3 是一个非常好的加速,但它很难实现——而且我不是这类问题的专家(这就是为什么它将是我的最后一个考虑)。 #1没有讨论。 #2 很有趣——你确定吗?
      • #2 根据我的观察是不正确的。使用缓冲流非常重要——它为套接字上的 i/o 操作提供了显着的性能提升。
      • @Haroogan :好的,我将删除#2。但请记住在将图像写入 BufferedOutputStream 后使用 flush()。
      • 我正在将ImageCapsule(其中包含带有编码图像的字节数组)写入ObjectOutputStream,后者又包装BufferedOutputStream,后者包装GZIPOutputStream,最终包装套接字的OutputStream :)因此,我只能在ObjectOutputStreamflush。但我想它会导致flush 调用链对包装链中的各个流进行调用,不是吗?
      【解决方案5】:

      我认为最大的带宽浪费在于桌面的完全转移。您应该将其视为电影,并对帧进行差分编码。

      缺点是处理比较复杂。也许那里有一些简单的视频编解码器 API/实现?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-25
        • 1970-01-01
        • 2010-09-22
        • 2011-03-21
        • 2010-12-26
        • 1970-01-01
        相关资源
        最近更新 更多