我创建了一个远程桌面控制应用程序。显然,它由客户端和服务器部分组成:
服务器:
- 从客户端接收鼠标/键盘动作;
- 将桌面截图发送给客户端。
客户:
- 从服务器接收截图;
- 发送鼠标/键盘动作;
考虑发送屏幕截图。当我将家用 PC 用作服务器时,我最终会得到 1920x1080 的屏幕截图尺寸。通过使用JAI Image I/O Tools并将其编码为 PNG,我能够为如此大的图像实现以下统计信息:
- 写入时间~0.2 s;(不是进入套接字,而是进入一些“常规”输出流,即编码时间)
- 读取时间~0.05 s;(不是来自套接字,而是来自一些“常规”输入流,即解码时间)
- 大小 ~250 KB;
- 完美的品质。
因此,根据#1 - 理想的可能 FPS 应该是 ~5。
不幸的是,我什至无法达到 5 FPS,甚至 2 FPS。我搜索了瓶颈,发现向/从套接字 I/O 流写入/读取最多需要约 2 秒(请参阅附录 1 和 2 进行澄清)。这当然是不可接受的。
我对该主题进行了一些研究 - 并在两侧添加了套接字 I/O 流的缓冲(使用BufferedInputStream
and )。BufferedOutputStream
我从 64 KB 大小开始。这确实提高了性能。但仍然不能有至少 2 FPS!此外,我已经尝试过Socket#setReceiveBufferSize
andSocket#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 提供高质量的实时桌面传输——他们是如何做到的?
附录一:添加客户端套接字读取的展开伪代码(@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
类,如何管理和使用发送和接收缓冲区(这是性能的主要关键)。