我已经创建了一个远程桌面控制应用程序。显然,它由客户端和服务器部分组成:
服务器
客户端:
考虑发送屏幕截图。当我使用家用PC作为服务器时 - 我最终获得了1920x1080的屏幕截图尺寸。通过使用JAI Image I/O Tools并将其编码为PNG,我能够为这么大的图像获得以下统计数据:
因此,根据#1 - 理想的可能FPS应为~5。
不幸的是,我甚至无法达到~5 FPS,甚至不能达到2 FPS。我搜索了瓶颈,发现写入/读取套接字I / O流需要大约2秒(请参阅附录1和2以获得澄清)。当然这是不可接受的。
我对这个主题进行了一些研究 - 并在两侧增加了套接字I / O流(BufferedInputStream
和BufferedOutputStream
)的缓冲。我从64 KB大小开始。这确实提高了性能。但仍然不能拥有至少2个FPS!另外,我已尝试使用Socket#setReceiveBufferSize
和Socket#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
问题:
Socket#setReceiveBufferSize
和
Socket#setSendBufferSize
行为。附录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
类幕后发生的事情,如何发送管理和利用接收缓冲区(这是性能的主要关键)。
答案 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,您将能够以增加带宽消耗为代价加速套接字通信。