Я создал приложение для удаленного управления рабочим столом. Очевидно, он состоит из клиентской и серверной частей:
Сервер:
Клиент:
Рассмотрите возможность отправки снимка экрана. Когда я использую свой домашний ПК в качестве сервера, я получаю размер скриншота 1920x1080. Используя JAI Image I/O Toolsи закодировав его как PNG, я смог получить следующие характеристики для такого большого изображения:
В итоге, согласно №1, идеально возможный FPS должен быть ~5.
К сожалению, я не могу добиться даже этих ~5 кадров в секунду, даже 2 кадров в секунду. Я поискал узкое место и обнаружил, что запись/чтение в/из потоков ввода/вывода сокетовзанимает до ~2 с (пояснения см. в приложениях 1 и 2). Безусловно, это неприемлемо.
Я немного изучил тему и добавил буферизацию потоков ввода-вывода сокета (с помощью BufferedInputStream
и BufferedOutputStream
) с обеих сторон. Я начал с размеров 64 КБ. Это действительно улучшило производительность. Но все еще не может быть хотя бы 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
.Приложение 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 в Java, второе издание: Практическое руководство для программистов, особенно главу 6 «Под капотом», в которой описывается что происходит за кулисами классов *Socket
, как управляются и используются буферы отправки и получения (которые являются основными ключами к производительности).