调整Java套接字的性能

时间:2012-05-16 08:45:08

标签: java performance sockets networking

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

服务器

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

客户端:

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

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

  1. 写入时间~0.2秒; (不是插入套接字,而是进入某些“常规”输出流,即编码时间
  2. 读取时间~0.05秒; (不是来自套接字,而是来自某些“常规”输入流,即解码时间
  3. 大小~250 KB;
  4. 完美品质。
  5. 因此,根据#1 - 理想的可能FPS应为~5。

    不幸的是,我甚至无法达到~5 FPS,甚至不能达到2 FPS。我搜索了瓶颈,发现写入/读取套接字I / O流需要大约2秒(请参阅附录1和2以获得澄清)。当然这是不可接受的。

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

    查看初始化代码:

    服务器

        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提供高质量的实时桌面传输 - 他们是如何做到的?
    5. 附录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类幕后发生的事情,如何发送管理和利用接收缓冲区(这是性能的主要关键)。

5 个答案:

答案 0 :(得分:11)

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

答案 1 :(得分:7)

  • 如果客户端为每个图像打开新的TCP连接,则TCP slow start可能会导致速度变慢。 - &GT;对所有帧使用相同的TCP连接。

  • TCP有自己的缓冲区,用于TCP的最佳方式 - &gt; BufferedOutputStream有时会带来好处,有时却没有。

  • 通常屏幕截图中只有一小部分内容会发生变化。 - &GT;仅转移更换的部件。

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

问题很清楚。 我上面的人建议使用多个nio通道,我宁愿使用多个阻塞套接字(nio不是更快)。然而,这并没有改善net comm,这是并行编程,在你的情况下肯定是一个很好的解决方案。 我建议如下

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

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