调整Java套接字的性能
我已经创建了一个远程桌面控制应用程序。 显然,它由客户端和服务器部分组成:
服务器:
- 从客户端接收鼠标/键盘操作;
- 将桌面截图发送给客户端。
客户:
- 从服务器接收屏幕截图;
- 发送鼠标/键盘动作;
考虑发送屏幕截图。 当我使用家用PC作为服务器时 – 我最终获得了1920×1080的屏幕截图尺寸。 通过使用JAI Image I / O Tools并将其编码为PNG,我能够为这样一个大图像获得以下统计数据:
- 写时间~0.2秒; (不是插入套接字,而是进入某些“常规”输出流, 即编码时间 )
- 读取时间~0.05秒; (不是来自套接字,而是来自某些“常规”输入流, 即解码时间 )
- 大小~250 KB;
- 完美的品质。
因此,根据#1 – 理想的可能FPS应为~5。
不幸的是,我甚至无法达到~5 FPS,甚至不能达到2 FPS。 我搜索了瓶颈,发现对套接字I / O流的写入/读取需要大约2秒( 参见附录1和2以进行说明 )。 当然这是不可接受的。
我对这个主题进行了一些研究 – 并在两侧增加了对套接字I / O流(使用BufferedInputStream
和BufferedOutputStream
)的BufferedOutputStream
。 我从64 KB大小开始。 这确实提高了性能。 但仍然不能拥有至少2个FPS! 另外,我已经尝试过Socket#setReceiveBufferSize
和Socket#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
问题:
- 对于所有这些案例,您会推荐哪些值(以提高性能)?为什么?
- 请澄清
Socket#setReceiveBufferSize
和Socket#setSendBufferSize
行为。 - 您可以提出哪些其他方法/技术来提高此类应用的性能?
- 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章“Under the Hood”,它描述了幕后发生的事情。 *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
有时会带来好处,有时则不会。 -
通常只有屏幕截图的一小部分在截屏之间发生变化。 – >只传输更换的部件。
我认为最大的带宽浪费将在于桌面的完全转移。 你应该把它更像电影,并对帧进行差分编码。
缺点是处理更复杂。 也许那里有一些简单的video编解码器API /实现?
您可以查看Java NIO( http://www.cs.brown.edu/courses/cs161/papers/j-nio-ltr.pdf ),使用多个频道等。
问题很清楚。 我上面的人建议使用多个nio通道,我宁愿使用多个阻塞套接字(nio不是更快)。 然而,这并没有改善net comm,这是并行编程,在你的情况下肯定是一个很好的解决方案。 我建议如下
http://docs.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html#setTcpNoDelay(boolean)
将其设置为true,您将能够以增加带宽消耗为代价加速套接字通信。