Ale*_*aev 18 java sockets performance networking
我已经创建了一个远程桌面控制应用程序.显然,它由客户端和服务器部分组成:
服务器:
客户:
考虑发送屏幕截图.当我使用家用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
速度有一些变化,但我不知道它们的行为究竟如何,因此我不知道使用哪些值.
看初始化代码:
服务器:
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
Run Code Online (Sandbox Code Playgroud)
客户:
Socket socket = new Socket(...);
socket.setSendBufferSize( ? ); // #6
socket.setReceiveBufferSize( ? ); // #7
OutputStream outputStream = new BufferedOutputStream(
socket.getOutputStream(), ? ); // #8
InputStream inputStream = new BufferedInputStream(
socket.getInputStream(), ? ); // #9
Run Code Online (Sandbox Code Playgroud)
问题:
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();
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
附录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.
}
}
Run Code Online (Sandbox Code Playgroud)
结论:
感谢您的回答,特别是EJP - 他让我的事情变得更加清晰.如果你像我一样 - 寻求如何调整套接字性能的答案,你一定要检查Java中的TCP/IP套接字,第二版:程序员实用指南,尤其是第6章"Under the Hood",它描述了幕后发生的事情.*Socket
类,如何管理和利用发送和接收缓冲区(这是性能的主要关键).
use*_*421 11
- 写入时间~0.2秒;
- 读取时间~0.05 s;
如果不考虑中间网络的延迟和带宽,这些目标就毫无意义.
大小~250 KB;
无关.图像大小取决于您,它与实际编程没有关系,这是此站点的目的.
完美的品质.
"完美品质"只要求您不要丢弃任何位,而这些位无论如何都不会通过TCP获得.
serverSocket.setReceiveBufferSize( ? ); // #1
Run Code Online (Sandbox Code Playgroud)
这为所有接受的套接字设置了接收缓冲区大小.设置它尽可能大,超过64k,如果可能的话.
socket.setSendBufferSize( ? ); // #6
Run Code Online (Sandbox Code Playgroud)
设置这个尽可能大,超过64k,如果可能的话.
socket.setReceiveBufferSize( ? ); // #7
Run Code Online (Sandbox Code Playgroud)
由于这是一个被接受的套接字,你已经在上面做了这个.去掉.
OutputStream outputStream = new BufferedOutputStream(
socket.getOutputStream(), ? ); // #8
InputStream inputStream = new BufferedInputStream(
socket.getInputStream(), ? ); // #9
Run Code Online (Sandbox Code Playgroud)
这些的默认值是8k; 只要你有足够的套接字缓冲区大小.
您会为所有这些案例推荐哪些值(以提高性能)以及为什么?
往上看.
请澄清
Socket#setReceiveBufferSize()
和Socket#setSendBufferSize()
行为.
它们控制TCP"窗口"的大小.这是一个相当深奥的话题,但其目的是使大小至少等于网络的带宽延迟乘积,即以秒为单位的带宽(以字节为单位/秒)> =以字节为单位的缓冲区大小.
您可以提出哪些其他方法/技术来提高此类应用的性能?
在发送数据时,不要抱着睡觉和做其他任务.尽可能快地发送它,你可以安排最紧密的循环.
Skype提供高质量的实时桌面传输 - 他们是如何做到的?
除非Skype员工碰巧想在这里泄露公司机密,否则可能不可知.