Я создал приложение для управления удаленным рабочим столом. Очевидно, что она состоит из клиентской и серверной частей:Тонкая производительность сокетов Java
Сервер:
- Получая действия мыши/клавиатуры от клиента;
- Отправка снимка экрана с рабочего стола клиенту.
Клиент:
- Получение скриншота с сервера;
- Отправка действий мыши/клавиатуры;
Рассмотрите возможность отправки скриншота. Когда я использую свой домашний компьютер в качестве сервера, я получаю размер скриншота 1920x1080. При использовании JAI Image I/O Tools и кодирования его в качестве PNG я смог достичь следующих статистике для такой большой картинки:
- Время записи ~ 0,2 с; (не в сокет, а в некоторый «обычный» выходной поток, i. время кодирования)
- Время чтения ~ 0,05 с; (не из сокета, а из некоторого «регулярного» входного потока, i. время декодирования)
- Размер ~ 250 КБ;
- Отличное качество.
В результате, согласно № 1, идеальным вариантом FPS должно быть ~ 5.
К сожалению, я не могу достичь даже тех ~ 5 FPS, даже не 2 FPS. Я искал узкое место и выяснил, что запись/чтение в/из потоков входов/выходов сокетов занимает до ~ 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
поведение. - Какие другие методы/методы вы можете посоветовать улучшить производительность такого приложения?
- 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 - он сделал вещи более ясными для меня. Если вы похожи на меня - ища ответы на вопрос о том, как настроить производительность сокетов, вы должны обязательно проверить TCP/IP Sockets in Java, Second Edition: Practical Guide for Programmers, особенно в главе 6 «Under the Hood», которая описывает, что происходит за кулисами классов *Socket
, о том, как управлять и использовать буферы отправки и получения (которые являются первичными ключами к производительности).
Вы уверены, что настройки сокета являются вашими ограничивающими факторами? Какая у вас связь между вашим клиентом и вашим сервером? Какова скорость вашего сервера? –
Можете ли вы опубликовать код, который вы читаете из сокетов на стороне клиента? – mcfinnigan
http://en.wikipedia.org/wiki/Delta_encoding – Krrose27