2012-05-16 5 views
17

Я создал приложение для управления удаленным рабочим столом. Очевидно, что она состоит из клиентской и серверной частей:Тонкая производительность сокетов Java

Сервер:

  • Получая действия мыши/клавиатуры от клиента;
  • Отправка снимка экрана с рабочего стола клиенту.

Клиент:

  • Получение скриншота с сервера;
  • Отправка действий мыши/клавиатуры;

Рассмотрите возможность отправки скриншота. Когда я использую свой домашний компьютер в качестве сервера, я получаю размер скриншота 1920x1080. При использовании JAI Image I/O Tools и кодирования его в качестве PNG я смог достичь следующих статистике для такой большой картинки:

  1. Время записи ~ 0,2 с; (не в сокет, а в некоторый «обычный» выходной поток, i. время кодирования)
  2. Время чтения ~ 0,05 с; (не из сокета, а из некоторого «регулярного» входного потока, i. время декодирования)
  3. Размер ~ 250 КБ;
  4. Отличное качество.

В результате, согласно № 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 

Вопросы:

  1. Какие значения вы бы рекомендовали (для повышения производительности) для всех этих случаев и почему?
  2. Просьба уточнить Socket#setReceiveBufferSize и Socket#setSendBufferSize поведение.
  3. Какие другие методы/методы вы можете посоветовать улучшить производительность такого приложения?
  4. 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, о том, как управлять и использовать буферы отправки и получения (которые являются первичными ключами к производительности).

+0

Вы уверены, что настройки сокета являются вашими ограничивающими факторами? Какая у вас связь между вашим клиентом и вашим сервером? Какова скорость вашего сервера? –

+0

Можете ли вы опубликовать код, который вы читаете из сокетов на стороне клиента? – mcfinnigan

+2

http://en.wikipedia.org/wiki/Delta_encoding – Krrose27

ответ

11
  • Время записи ~ 0,2 с;
  • Время чтения ~ 0,05 с;

Эти цели совершенно бессмысленны без учета латентности и ширины полосы промежуточной сети.

Размер ~ 250 КБ;

Отключить тему. Размер изображения зависит от вас, и он не имеет отношения к реальному программированию, которое является целью этого сайта.

Отличное качество.

«Отличное качество» требует только того, чтобы вы не отбрасывали какие-либо биты, которые вы не получите в любом случае через 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 не захочет раздавать фирменные секреты здесь.

+0

Спасибо за эту информацию. Тем не менее, я должен не согласиться с вами о времени записи/чтения изображений. Посмотрите на оригинальный пост - я добавил разъяснения там. Они записывают/читают не в сам сокет, а в «обычные» потоки ввода-вывода. Поэтому на этот раз отображается среднее время, необходимое для кодирования/декодирования PNG. Подумайте об этом - я не могу отправлять PNG быстрее, чем каждые ~ 0,2 с - независимо от скорости соединения; Я не могу читать PNG быстрее, чем каждые ~ 0,05 с - независимо от скорости соединения; –

+0

@Haroogan. Я хочу сказать, что вы не можете читать или писать быстрее, чем сеть, поэтому указание времени для каждого бесполезно. – EJP

+0

То же самое касается размера изображения - я чувствовал, что это важно для рассмотрения размеров буферов, не так ли? –

1

Я думаю, что самые большие пропускная способность отходы будут находиться в полной передаче рабочего стола. Вы должны относиться к этому скорее как к фильму и делать дифференциальное кодирование кадров.

Недостатком является более сложное управление. Может быть, есть некоторые простые API-интерфейсы/кодеки видео кодеков?

5
  • Если клиент открывает новое соединение TCP для каждого изображения, то TCP slow start может привести к медлительности. -> Использовать то же TCP-соединение для всех фреймов.

  • TCP имеют собственные буферы, которые используются оптимальным способом для TCP ->BufferedOutputStream дает иногда выгоду, а иногда и нет.

  • Обычно только небольшая часть экрана изменяется между снимками экрана. -> Перенести только измененные детали.

+0

Я знаю, что # 3 - очень хорошее ускорение, но это трудно реализовать - и я не эксперт в таких проблемах (поэтому это будет мое последнее соображение). # 1 не обсуждается. # 2 интересно - вы уверены? –

+0

# 2 является неправильным согласно моим наблюдениям. Использование буферизованных потоков Чрезвычайно важно - это обеспечивает значительное повышение производительности для операций ввода-вывода в сокетах. –

+0

@ Харооган: Хорошо, я удалю # 2. Но не забудьте использовать flush() после записи изображения в BufferedOutputStream. – SKi

0

Вопрос ясен. Кто-то выше меня предлагает использование нескольких каналов nio, я бы предпочел использовать несколько блокирующих сокетов (nio не быстрее). Однако это не улучшает сеть, это параллельное программирование, которое определенно было бы хорошим решением в вашем случае. Позвольте мне предложить следующее

http://docs.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html#setTcpNoDelay(boolean)

установив его так, вы сможете ускорить штуцеры комм вверх по стоимости увеличения потребления пропускной способности.

+0

Но влияние на производительность будет на уровне узла. – navaltiger