Реализация удаленного сервера кадрового буфера в Java
Опубликовано: 2022-03-11В вычислительной технике Virtual Network Computing (VNC) — это система совместного использования графического рабочего стола, которая использует протокол Remote Framebuffer (RFB) для удаленного управления другим компьютером. Он передает события клавиатуры и мыши с одного компьютера на другой и ретранслирует обновления графического экрана обратно в другом направлении по сети.
RFB — это простой протокол для удаленного доступа к графическим пользовательским интерфейсам. Поскольку он работает на уровне кадрового буфера, он применим ко всем оконным системам и приложениям, включая Microsoft Windows, Mac OS X и X Window System.
В этой статье я покажу, как реализовать серверный протокол RFB, и продемонстрирую с помощью небольшого Java-приложения Swing, как передать главное окно через TCP-соединение в программы просмотра VNC. Идея состоит в том, чтобы продемонстрировать основные возможности протокола и возможную реализацию на Java.
Читатель должен иметь базовые знания языка программирования Java и должен быть знаком с основными понятиями сетей TCP/IP, модели клиент-сервер и т. д. В идеале читатель должен быть разработчиком Java и иметь некоторый опыт работы с известными реализациями VNC, такими как RealVNC. , UltraVNC, TightVNC и т. д.
Спецификация протокола удаленного буфера кадров
Спецификация протокола RFB довольно хорошо определена. Согласно Википедии, протокол RFB имеет несколько версий. В этой статье мы сосредоточимся на общих сообщениях, которые должны правильно пониматься большинством реализаций VNC независимо от версии протокола.
После того, как средство просмотра VNC (клиент) устанавливает TCP-соединение с сервером VNC (службой RFB), на первом этапе происходит обмен версией протокола:
RFB Service ----------- "RFB 003.003\n" -------> VNC viewer RFB Service <---------- "RFB 003.008\n" -------- VNC viewer
Это простой поток байтов, который можно декодировать в символы ASCII, например «RFB 003.008\n».
Как только это будет сделано, следующим шагом будет аутентификация. Сервер VNC отправляет массив байтов, чтобы указать, какой тип аутентификации он поддерживает. Например:
RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer
Здесь сервер VNC отправил только 1 возможный тип аутентификации (0x02). Первый байт 0x01 обозначает количество доступных типов аутентификации. Средство просмотра VNC должно ответить значением 0x02, так как это единственный возможный тип, поддерживаемый сервером в этом примере.
Затем сервер отправит запрос аутентификации (в зависимости от того, какой алгоритм существует, их несколько), а клиент должен ответить соответствующим сообщением ответа на вызов и дождаться подтверждения ответа сервером. После аутентификации клиента он может продолжить процесс установления сеанса.
Самый простой способ здесь — вообще отказаться от аутентификации. Протокол RFB в любом случае небезопасен, независимо от механизма аутентификации. Если важна безопасность, правильным способом будет туннелирование сеансов RFB через VPN или SSH-соединения.
В этот момент средство просмотра VNC отправляет сообщение об общем рабочем столе, в котором сообщается, будет ли клиент предоставлять общий доступ и разрешать другим средствам просмотра VNC подключаться к тому же рабочему столу. Реализация службы RFB должна учесть это сообщение и, возможно, предотвратить совместное использование одного и того же экрана несколькими зрителями VNC. Это сообщение имеет длину всего 1 байт, допустимое значение — 0x00 или 0x01.
Наконец, сервер RFB отправляет сообщение инициализации сервера, которое содержит размер экрана, количество битов на пиксель, глубину, флаг прямого порядка байтов и флаги истинного цвета, максимальные значения для красного, зеленого и синего цветов, позиции битов в пикселях для красного, зеленого и синего цветов. , и строка/название рабочего стола. Первые два байта представляют ширину экрана в пикселях, следующие два байта — высоту экрана. После байтов высоты экрана в сообщении должны присутствовать биты на байт пикселя. Обычно это значение равно 8, 16 или 32. В большинстве современных систем с полным цветовым диапазоном количество битов на пиксельный байт имеет значение 32 (0x20). Он сообщает клиенту, что он может запросить полный цвет для каждого пикселя с сервера. Байт с обратным порядком байтов не равен нулю, только если пиксели расположены в порядке с обратным порядком байтов. Если байт истинного цвета отличен от нуля (истина), то следующие шесть байтов определяют, как извлечь интенсивность красного, зеленого и синего цветов из значения пикселя. Следующие шесть байтов являются максимально допустимыми значениями для красного, зеленого и синего компонентов пикселя. Это важно в 8-битном цветовом режиме, где для каждого компонента цвета доступно лишь несколько битов. Красный, зеленый и синий сдвиги определяют позиции битов для каждого цвета. Последние три байта являются дополнением и должны игнорироваться клиентом. После формата пикселя идет байт, определяющий длину строки заголовка рабочего стола. Заголовок рабочего стола представляет собой строку в кодировке ASCII в массиве байтов произвольной длины.
После сообщения об инициализации сервера служба RFB должна читать клиентские сообщения из сокета и декодировать их. Существует 6 типов сообщений:
- SetPixelFormat
- SetEncodings
- FramebufferUpdateRequest
- Ключевое событие
- PointerEvent
- КлиентCutText
Документация протокола довольно точна и объясняет каждое сообщение. Для каждого сообщения объясняется каждый байт. Например, сообщение инициализации сервера:
Количество байтов | Тип | Описание |
---|---|---|
2 | U16 | ширина буфера кадра |
2 | U16 | высота кадрового буфера |
16 | PIXEL_FORMAT | серверный пиксельный формат |
4 | U32 | длина имени |
длина имени | Массив U8 | строка имени |
Здесь PIXEL_FORMAT:
Количество байтов | Тип | Описание |
---|---|---|
1 | U8 | бит на пиксель |
1 | U8 | глубина |
1 | U8 | флаг с прямым порядком байтов |
1 | U8 | флаг истинного цвета |
2 | U16 | красный макс |
2 | U16 | зеленый макс |
2 | U16 | синий макс |
1 | U8 | красное смещение |
1 | U8 | зеленый сдвиг |
1 | U8 | синий сдвиг |
3 | набивка |
U16 означает беззнаковое 16-битное целое (два байта), U32 — беззнаковое 32-битное целое, U8 массив — массив байтов и т. д.
Реализация протокола на Java
Типичное серверное приложение Java состоит из одного потока, прослушивающего клиентские соединения, и нескольких потоков, обрабатывающих клиентские соединения.
/* * Use TCP port 5902 (display :2) as an example to listen. */ int port = 5902; ServerSocket serverSocket; serverSocket = new ServerSocket(port); /* * Limit sessions to 100. This is lazy way, if * somebody really open 100 sessions, server socket * will stop listening and no new VNC viewers will be * able to connect. */ while (rfbClientList.size() < 100) { /* * Wait and accept new client. */ Socket client = serverSocket.accept(); /* * Create new object for each client. */ RFBService rfbService = new RFBService(client); /* * Add it to list. */ rfbClientList.add(rfbService); /* * Handle new client session in separate thread. */ (new Thread(rfbService, "RFBService" + rfbClientList.size())).start(); }
Здесь был выбран TCP-порт 5902 (отображается: 2), и цикл while ожидает подключения клиента. Метод ServerSocket.accept() блокируется и заставляет поток ожидать нового подключения клиента. После подключения клиента создается новый поток RFBService, который обрабатывает сообщения протокола RFB, полученные от клиента.
Класс RFBService реализует интерфейс Runnable. Он полон методов для чтения байтов из сокета. Важен метод run() , который выполняется сразу же при запуске потока в конце цикла:
@Override public void run() { try { /* * RFB server has to send protocol version string first. * And wait for VNC viewer to replay with * protocol version string. */ sendProtocolVersion(); String protocolVer = readProtocolVersion(); if (!protocolVer.startsWith("RFB")) { throw new IOException(); }
Здесь метод sendProtocolVersion() отправляет строку RFB клиенту (программе просмотра VNC), а затем считывает строку версии протокола с клиента. Клиент должен ответить что-то вроде «RFB 003.008\n». Метод readProtocolVersion() конечно блокирующий, как и любой метод, имя которого начинается со слова read.
private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }
Метод readProtocolVersion() прост: он читает 12 байт из сокета и возвращает строковое значение. Функция readU8Array(int) считывает заданное количество байтов, в данном случае 12 байтов. Если в сокете недостаточно байтов для чтения, он ждет:
private byte[] readU8Array(int len) throws IOException { byte[] buffer = new byte[len]; int offset = 0, left = buffer.length; while (offset < buffer.length) { int numOfBytesRead = 0; numOfBytesRead = in.read(buffer, offset, left); offset = offset + numOfBytesRead; left = left - numOfBytesRead; } return buffer; }
Аналогично readU8Array(int) существуют методы readU16int() и readU32int() , которые считывают байты из сокета и возвращают целочисленное значение.
После отправки версии протокола и прочтения ответа служба RFB должна отправить сообщение безопасности:
/* * RFB server sends security type bytes that may request * a user to type password. * In this implementation, this is set to simples * possible option: no authentication at all. */ sendSecurityType();
В этой реализации выбран самый простой способ: не требовать никакого пароля со стороны клиента VNC.
private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }
где SECURITY_TYPE — массив байтов:
private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};
Этот массив байтов по протоколу RFB версии 3.3 означает, что программе просмотра VNC не нужно отправлять какой-либо пароль.
Следующее, что служба RFB должна получить от клиента, это флаг общего рабочего стола. Это один байт на сокете.
/* * RFB server reads shared desktop flag. It's a single * byte that tells RFB server * should it support multiple VNC viewers connected at * same time or not. */ byte sharedDesktop = readSharedDesktop();
Как только флаг общего рабочего стола будет прочитан из сокета, мы игнорируем его в нашей реализации.
Служба RFB должна отправить сообщение инициализации сервера:
/* * RFB server sends ServerInit message that includes * screen resolution, * number of colors, depth, screen title, etc. */ screenWidth = JFrameMainWindow.jFrameMainWindow.getWidth(); screenHeight = JFrameMainWindow.jFrameMainWindow.getHeight(); String windowTitle = JFrameMainWindow.jFrameMainWindow.getTitle(); sendServerInit(screenWidth, screenHeight, windowTitle);
Класс JFrameMainWindow — это JFrame, который используется здесь для демонстрационных целей в качестве источника графики. Сообщение об инициализации сервера имеет обязательную ширину и высоту экрана в пикселях, а также заголовок рабочего стола. В данном примере это заголовок JFrame, полученный методом getTitle().
После сообщения об инициализации сервера поток службы RFB зацикливается, читая из сокета шесть типов сообщений:
/* * Main loop where clients messages are read from socket. */ while (true) { /* * Mark first byte and read it. */ in.mark(1); int messageType = in.read(); if (messageType == -1) { break; } /* * Go one byte back. */ in.reset(); /* * Depending on message type, read complete message on socket. */ if (messageType == 0) { /* * Set Pixel Format */ readSetPixelFormat(); } else if (messageType == 2) { /* * Set Encodings */ readSetEncoding(); } else if (messageType == 3) { /* * Frame Buffer Update Request */ readFrameBufferUpdateRequest(); } else if (messageType == 4) { /* * Key Event */ readKeyEvent(); } else if (messageType == 5) { /* * Pointer Event */ readPointerEvent(); } else if (messageType == 6) { /* * Client Cut Text */ readClientCutText(); } else { err("Unknown message type. Received message type = " + messageType); } }
Каждый метод readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() блокирует и запускает какое-то действие.

Например, метод readClientCutText() считывает текст, который закодирован в сообщении, когда пользователь вырезает текст на стороне клиента, а затем программа просмотра VNC отправляет текст по протоколу RFB на сервер. Затем текст помещается на стороне сервера в буфер обмена.
Сообщения клиентов
Все шесть сообщений должны поддерживаться службой RFB, по крайней мере, на уровне байтов: когда клиент отправляет сообщение, необходимо прочитать полную длину байта. Это связано с тем, что протокол RFB ориентирован на байты и между двумя сообщениями нет границы.
Наиболее важным сообщением является запрос на обновление кадрового буфера. Клиент может запросить полное обновление или добавочное обновление экрана.
private void readFrameBufferUpdateRequest() throws IOException { int messageType = in.read(); int incremental = in.read(); if (messageType == 0x03) { int x_pos = readU16int(); int y_pos = readU16int(); int width = readU16int(); int height = readU16int(); screenWidth = width; screenHeight = height; if (incremental == 0x00) { incrementalFrameBufferUpdate = false; int x = JFrameMainWindow.jFrameMainWindow.getX(); int y = JFrameMainWindow.jFrameMainWindow.getY(); RobotScreen.robo.getScreenshot(x, y, width, height); sendFrameBufferUpdate(x_pos, y_pos, width, height, 0, RobotScreen.robo.getColorImageBuffer()); } else if (incremental == 0x01) { incrementalFrameBufferUpdate = true; } else { throw new IOException(); } } else { throw new IOException(); } }
Первый байт сообщения запроса кадрового буфера является типом сообщения. Значение всегда 0x03. Следующий байт — это добавочный флаг, который указывает серверу отправлять полный кадр или просто разницу. В случае запроса на полное обновление служба RFB сделает скриншот главного окна с помощью класса RobotScreen и отправит его клиенту.
Если это добавочный запрос, для флага incrementalFrameBufferUpdate будет установлено значение true. Этот флаг будет использоваться компонентами Swing для проверки необходимости отправки измененных частей экрана. Обычно JMenu, JMenuItem, JTextArea и т. д. должны выполнять добавочное обновление экрана, когда пользователь перемещает указатель мыши, щелкает, отправляет нажатие клавиши и т. д.
Метод sendFrameBufferUpdate(int, int, int, int, int[]) сбрасывает буфер изображения в сокет.
public void sendFrameBufferUpdate(int x, int y, int width, int height, int encodingType, int[] screen) throws IOException { if (x + width > screenWidth || y + height > screenHeight) { err ("Invalid frame update size:"); err (" x = " + x + ", y = " + y); err (" width = " + width + ", height = " + height); return; } byte messageType = 0x00; byte padding = 0x00; out.write(messageType); out.write(padding); int numberOfRectangles = 1; writeU16int(numberOfRectangles); writeU16int(x); writeU16int(y); writeU16int(width); writeU16int(height); writeS32int(encodingType); for (int rgbValue : screen) { int red = (rgbValue & 0x000000FF); int green = (rgbValue & 0x0000FF00) >> 8; int blue = (rgbValue & 0x00FF0000) >> 16; if (bits_per_pixel == 8) { out.write((byte) colorMap.get8bitPixelValue(red, green, blue)); } else { out.write(red); out.write(green); out.write(blue); out.write(0); } } out.flush(); }
Метод проверяет, чтобы координата (x, y) не уходила за пределы экрана вместе с шириной x высотой буфера изображения. Значение типа сообщения для обновления кадрового буфера — 0x00. Значение заполнения обычно равно 0x00 и должно игнорироваться средством просмотра VNC. Количество прямоугольников представляет собой двухбайтовое значение и определяет, сколько прямоугольников следует за сообщением.
Каждый прямоугольник имеет верхнюю левую координату, ширину и высоту, тип кодирования и данные пикселей. Можно использовать несколько эффективных форматов кодирования, таких как zrle, hextile и плотный. Однако, чтобы все было просто и понятно, в нашей реализации мы будем использовать необработанное кодирование.
Необработанное кодирование означает, что цвет пикселя передается как компонент RGB. Если клиент установил кодировку пикселя как 32-битную, то для каждого пикселя передается 4 байта. Если клиент использует 8-битный цветовой режим, то каждый пиксель передается как 1 байт. Код показан в цикле for. Обратите внимание, что для 8-битного режима цветовая карта используется для поиска наилучшего соответствия для каждого пикселя из снимка экрана / буфера изображения. Для режима 32-битных пикселей буфер изображения содержит массив целых чисел, каждое значение имеет мультиплексированные компоненты RGB.
Демонстрационное приложение Swing
Демо-приложение Swing содержит прослушиватель действий, который запускает метод sendFrameBufferUpdate(int, int, int, int, int[]) . Обычно элементы приложения, такие как компоненты Swing, должны иметь прослушиватели и отправлять изменения экрана клиенту. Например, когда пользователь вводит что-то в JTextArea, это должно быть передано в программу просмотра VNC.
public void actionPerformed(ActionEvent arg0) { /* * Get dimensions and location of main JFrame window. */ int offsetX = JFrameMainWindow.jFrameMainWindow.getX(); int offsetY = JFrameMainWindow.jFrameMainWindow.getY(); int width = JFrameMainWindow.jFrameMainWindow.getWidth(); int height = JFrameMainWindow.jFrameMainWindow.getHeight(); /* * Do not update screen if main window dimension has changed. * Upon main window resize, another action listener will * take action. */ int screenWidth = RFBDemo.rfbClientList.get(0).screenWidth; int screenHeight = RFBDemo.rfbClientList.get(0).screenHeight; if (width != screenWidth || height != screenHeight) { return; } /* * Capture new screenshot into image buffer. */ RobotScreen.robo.getScreenshot(offsetX, offsetY, width, height); int[] delta = RobotScreen.robo.getDeltaImageBuffer(); if (delta == null) { offsetX = 0; offsetY = 0; Iterator<RFBService> it = RFBDemo.rfbClientList.iterator(); while (it.hasNext()) { RFBService rfbClient = it.next(); if (rfbClient.incrementalFrameBufferUpdate) { try { /* * Send complete window. */ rfbClient.sendFrameBufferUpdate( offsetX, offsetY, width, height, 0, RobotScreen.robo.getColorImageBuffer()); } catch (SocketException ex) { it.remove(); } catch (IOException ex) { ex.printStackTrace(); it.remove(); } rfbClient.incrementalFrameBufferUpdate = false; } } } else { offsetX = RobotScreen.robo.getDeltaX(); offsetY = RobotScreen.robo.getDeltaY(); width = RobotScreen.robo.getDeltaWidth(); height = RobotScreen.robo.getDeltaHeight(); Iterator<RFBService> it = RFBDemo.rfbClientList.iterator(); while (it.hasNext()) { RFBService rfbClient = it.next(); if (rfbClient.incrementalFrameBufferUpdate) { try { /* * Send only delta rectangle. */ rfbClient.sendFrameBufferUpdate( offsetX, offsetY, width, height, 0, delta); } catch (SocketException ex) { it.remove(); } catch (IOException ex) { ex.printStackTrace(); it.remove(); } rfbClient.incrementalFrameBufferUpdate = false; } } } }
Код этого обработчика действий довольно прост: он делает скриншот главного окна JFrameMain с помощью класса RobotScreen, затем определяет, нужно ли частичное обновление экрана. Переменная diffUpdateOfScreen используется как флаг частичного обновления. И, наконец, клиенту передается полный буфер изображения или только разные строки. Этот код также учитывает больше подключенных клиентов, поэтому используется итератор, а список клиентов поддерживается в элементе RFBDemo.rfbClientList<RFBService> .
Слушатель действия обновления кадрового буфера можно использовать в таймере, который может быть запущен любым изменением JComponent:
/* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);
Этот код находится в конструкторе класса JFrameMainWindow. Таймер запускается в методе doIncrementalFrameBufferUpdate():
public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }
Другие прослушиватели действий обычно вызывают метод doIncrementalFrameBufferUpdate():
public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }
Этот путь должен быть простым и легким. Необходима только ссылка на экземпляр JFrameMainWindow и один вызов метода doIncrementalFrameBufferUpdate() . Метод проверит, есть ли подключенные клиенты, и если есть, то запустится таймер timerUpdateFrameBuffer . После запуска таймера прослушиватель действий фактически сделает снимок экрана и будет выполнен метод sendFrameBufferUpdate() .
На рисунке выше показано отношение слушателя к процедуре обновления кадрового буфера. Большинство слушателей запускаются, когда пользователь выполняет действие: щелкает, выбирает текст, печатает что-то в текстовой области и т. д. Затем выполняется функция-член doIncrementalFramebufferUpdate() , которая запускает таймер timerUpdateFrameBuffer . В конечном итоге таймер вызовет метод sendFrameBufferUpdate() в классе RFBService, что вызовет обновление экрана на стороне клиента (средство просмотра VNC).
Захват экрана, воспроизведение нажатий клавиш и перемещение указателя мыши на экране
Java имеет встроенный класс Robot, который позволяет разработчику написать приложение, которое будет захватывать снимки экрана, отправлять ключи, манипулировать указателем мыши, производить щелчки и т. д.
Чтобы захватить область экрана, где отображается окно JFrame, используется RobotScreen. Основной метод — это getScreenshot(int, int, int, int), который захватывает область экрана. Значения RGB для каждого пикселя хранятся в массиве int[]:
public void getScreenshot(int x, int y, int width, int height) { Rectangle screenRect = new Rectangle(x, y, width, height); BufferedImage colorImage = robot.createScreenCapture(screenRect); previousImageBuffer = colorImageBuffer; colorImageBuffer = ((DataBufferInt) colorImage.getRaster().getDataBuffer()).getData(); if (previousImageBuffer == null || previousImageBuffer.length != colorImageBuffer.length) { previousImageBuffer = colorImageBuffer; } this.width = width; this.height = height; }
Метод сохраняет пиксели в массиве colorImageBuffer. Для получения данных о пикселях можно использовать метод getColorImageBuffer() .
Метод также сохраняет предыдущий буфер изображения. Можно получить только измененные пиксели. Чтобы получить разницу только в области изображения, используйте метод getDeltaImageBuffer() .
Отправка нажатий клавиш в систему очень проста с классом Robot. Однако некоторые специальные коды клавиш, полученные от программ просмотра VNC, должны быть сначала правильно переведены. Класс RobotKeyboard имеет метод sendKey(int, int) , который обрабатывает специальные клавиши и буквенно-цифровые клавиши:
public void sendKey(int keyCode, int state) { switch (keyCode) { case 0xff08: doType(VK_BACK_SPACE, state); break; case 0xff09: doType(VK_TAB, state); break; case 0xff0d: case 0xff8d: doType(VK_ENTER, state); break; case 0xff1b: doType(VK_ESCAPE, state); break; … case 0xffe1: case 0xffe2: doType(VK_SHIFT, state); break; case 0xffe3: case 0xffe4: doType(VK_CONTROL, state); break; case 0xffe9: case 0xffea: doType(VK_ALT, state); break; default: /* * Translation of a..z keys. */ if (keyCode >= 97 && keyCode <= 122) { /* * Turn lower-case a..z key codes into upper-case A..Z key codes. */ keyCode = keyCode - 32; } doType(keyCode, state); } }
Состояние аргумента определяет, нажата ли клавиша или отпущена. После правильного преобразования кода клавиши в константу VT метод doType(int, int) передает значение ключа роботу, и эффект такой же, как если бы локальный пользователь нажал клавишу на клавиатуре:
private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }
Подобно RobotKeyboard, это класс RobotMouse, который обрабатывает события указателя и заставляет указатель мыши перемещаться и щелкать.
public void mouseMove(int x, int y) { robot.mouseMove(x, y); }
Все три класса RobotScreen, RobotMouse и RobotKeyboard выделяют новый экземпляр Robot в конструкторе:
this.robot = new Robot();
У нас есть только один экземпляр каждого из них, поскольку на уровне приложения нет необходимости иметь более одного экземпляра класса RobotScreen, RobotMouse или RobotKeyboard.
public static void main(String[] args) { ... /* * Initialize static Robot objects for screen, keyboard and mouse. */ RobotScreen.robo = new RobotScreen(); RobotKeyboard.robo = new RobotKeyboard(); RobotMouse.robo = new RobotMouse(); ... }
В этом демонстрационном приложении эти экземпляры создаются в функции main() .
Результатом является приложение на основе Swing на Java, которое действует как поставщик услуг RFB и позволяет стандартным средствам просмотра VNC подключаться к нему:
Заключение
Протокол RFB широко используется и принимается. Клиентские реализации в виде программ просмотра VNC существуют практически для всех платформ и устройств. Основная цель — удаленное отображение рабочих столов, но могут быть и другие приложения. Например, вы можете создавать изящные графические инструменты и получать к ним удаленный доступ, чтобы улучшить существующие удаленные рабочие процессы.
В этой статье рассказывается об основах протокола RFB, формате сообщения, о том, как отправить часть экрана и как работать с клавиатурой и мышью. Полный исходный код демонстрационного приложения Swing доступен на GitHub.