调整 Java 套接字的性能



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

服务器:

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

客户:

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

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

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

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

不幸的是,我甚至无法达到

~5 FPS,甚至无法达到2 FPS。我搜索了瓶颈,发现向/从套接字 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 - 他让我更清楚了。如果你像我一样 - 寻求如何调整套接字性能的答案,你绝对应该检查Java 中的 TCP/IP 套接字,第二版:程序员实用指南,特别是第 6 章"引擎盖下",它描述了*Socket类幕后发生的事情,发送和接收缓冲区是如何管理和使用的(这是性能的主要关键(。

  • 写入时间 ~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员工碰巧想在这里泄露公司机密。

  • 如果客户端为每个映像打开新的 TCP 连接,则 TCP 启动缓慢可能会导致运行缓慢。 --> 对所有帧使用相同的 TCP 连接。

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

  • 通常屏幕截图之间只有一小部分屏幕会更改。 --> 仅传输更改的部分。

我认为最大的带宽浪费将在于桌面的完全转移。你应该把它更像对待一部电影,并对帧进行差分编码。

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

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

http://docs.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html#setTcpNoDelay(布尔值(

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

你可以

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

最新更新