Implementación de un servidor Framebuffer remoto en Java
Publicado: 2022-03-11En informática, Virtual Network Computing (VNC) es un sistema de uso compartido de escritorio gráfico que utiliza el protocolo Remote Framebuffer (RFB) para controlar de forma remota otra computadora. Transmite eventos de teclado y mouse de una computadora a otra, y transmite actualizaciones de pantalla gráfica en la otra dirección a través de una red.
RFB es un protocolo simple para el acceso remoto a las interfaces gráficas de usuario. Debido a que funciona en el nivel del búfer de cuadros, es aplicable a todos los sistemas y aplicaciones de ventanas, incluidos Microsoft Windows, Mac OS X y X Window System.
En este artículo, mostraré cómo implementar el protocolo del lado del servidor RFB y demostraré con una pequeña aplicación Java Swing cómo transmitir la ventana principal a través de una conexión TCP a los visores VNC. La idea es demostrar las características básicas del protocolo y su posible implementación en Java.
El lector debe tener conocimientos básicos del lenguaje de programación Java y debe estar familiarizado con los conceptos básicos de redes TCP/IP, modelo cliente-servidor, etc. Idealmente, el lector es un desarrollador de Java y tiene algo de experiencia con implementaciones de VNC conocidas como RealVNC. , UltraVNC, TightVNC, etc.
Especificación del protocolo Framebuffer remoto
La especificación del protocolo RFB está bastante bien definida. Según Wikipedia, el protocolo RFB tiene varias versiones. Para este artículo, nuestro enfoque estará en los mensajes comunes que la mayoría de las implementaciones de VNC deben entender correctamente, independientemente de la versión del protocolo.
Después de que un visor VNC (cliente) establezca una conexión TCP con un servidor VNC (servicio RFB), la primera fase implica el intercambio de la versión del protocolo:
RFB Service ----------- "RFB 003.003\n" -------> VNC viewer RFB Service <---------- "RFB 003.008\n" -------- VNC viewer
Es un simple flujo de bytes que se puede decodificar en caracteres ASCII, como "RFB 003.008\n".
Una vez hecho esto, el siguiente paso es la autenticación. El servidor VNC envía una serie de bytes para indicar qué tipo de autenticación admite. Por ejemplo:
RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer
Aquí, el servidor VNC envió solo 1 tipo de autenticación posible (0x02). El primer byte 0x01 indica el número de tipos de autenticación disponibles. El visor de VNC debe responder con el valor 0x02, ya que ese es el único tipo posible admitido por el servidor en este ejemplo.
A continuación, el servidor enviará un desafío de autenticación (según el algoritmo, hay varios), y el cliente debe responder con el mensaje de respuesta de desafío adecuado y esperar a que el servidor confirme la respuesta. Una vez autenticado el cliente, puede continuar con el proceso de establecimiento de sesión.
La forma más sencilla aquí es elegir ninguna autenticación en absoluto. El protocolo RFB es inseguro de todos modos, independientemente del mecanismo de autenticación. Si la seguridad es importante, la forma adecuada sería tunelizar las sesiones de RFB a través de conexiones VPN o SSH.
En este punto, el visor VNC envía un mensaje de escritorio compartido que indica si el cliente compartirá y permitirá que otros visores VNC se conecten al mismo escritorio. Depende de la implementación del servicio RFB considerar ese mensaje y posiblemente evitar que varios espectadores de VNC compartan la misma pantalla. Este mensaje tiene solo 1 byte de longitud y un valor válido es 0x00 o 0x01.
Finalmente, el servidor RFB envía un mensaje de inicio del servidor, que contiene la dimensión de la pantalla, los bits por píxel, la profundidad, la bandera big endian y las banderas de color verdadero, los valores máximos para los colores rojo, verde y azul, las posiciones de bits en píxeles para los colores rojo, verde y azul. y cadena/título de escritorio. Los primeros dos bytes representan el ancho de la pantalla en píxeles, los siguientes dos bytes son la altura de la pantalla. Después de los bytes de altura de pantalla, los bits por byte de píxel deben estar presentes en el mensaje. El valor suele ser 8, 16 o 32. En la mayoría de los sistemas modernos con rango de color completo, los bits por byte de píxel tienen un valor de 32 (0x20). Le dice al cliente que puede solicitar a todo color para cada píxel del servidor. El byte big endian es distinto de cero solo si los píxeles están en orden big endian. Si el byte de color verdadero no es cero (verdadero), los siguientes seis bytes especifican cómo extraer las intensidades de color rojo, verde y azul del valor del píxel. Los siguientes seis bytes son valores máximos permitidos para el componente rojo, verde y azul del píxel. Esto es importante en el modo de color de 8 bits, donde solo hay unos pocos bits disponibles para cada componente de color. Los cambios de rojo, verde y azul determinan las posiciones de bits para cada color. Los últimos tres bytes son de relleno y el cliente debe ignorarlos. Después del formato de píxel, hay un byte que define la longitud de una cadena para el título del escritorio. El título del escritorio es una cadena codificada en ASCII en una matriz de bytes de longitud arbitraria.
Después del mensaje de inicialización del servidor, el servicio RFB debe leer los mensajes del cliente del socket y decodificarlos. Hay 6 tipos de mensajes:
- Establecer formato de píxel
- Establecer codificaciones
- Solicitud de actualización de Framebuffer
- Evento clave
- Evento de puntero
- ClienteCutText
La documentación del protocolo es bastante exacta y explica cada mensaje. Para cada mensaje, se explica cada byte. Por ejemplo, mensaje de inicio del servidor:
Nº de bytes | Escribe | Descripción |
---|---|---|
2 | U16 | framebuffer-ancho |
2 | U16 | framebuffer-height |
dieciséis | PIXEL_FORMAT | formato de píxel del servidor |
4 | U32 | nombre-longitud |
nombre-longitud | matriz U8 | cadena de nombre |
Aquí, PIXEL_FORMAT es:
Nº de bytes | Escribe | Descripción |
---|---|---|
1 | U8 | bits por píxel |
1 | U8 | profundidad |
1 | U8 | bandera-big-endian |
1 | U8 | bandera de color verdadero |
2 | U16 | rojo-máximo |
2 | U16 | green-max |
2 | U16 | azul-max |
1 | U8 | corrimiento al rojo |
1 | U8 | cambio verde |
1 | U8 | Cambio azúl |
3 | relleno |
U16 significa un entero de 16 bits sin signo (dos bytes), U32 es un entero de 32 bits sin signo, la matriz U8 es una matriz de bytes, etc.
Implementación de protocolo en Java
Una aplicación de servidor Java típica consta de un subproceso que escucha las conexiones de los clientes y varios subprocesos que manejan las conexiones de los clientes.
/* * 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(); }
Aquí se eligió el puerto TCP 5902 (mostrar: 2), y el bucle while espera a que se conecte un cliente. El método ServerSocket.accept() está bloqueando y hace que el subproceso espere una nueva conexión de cliente. Una vez que el cliente se conecta, se crea un nuevo subproceso RFBService que maneja los mensajes del protocolo RFB recibidos del cliente.
La clase RFBService implementa la interfaz Runnable. Está lleno de métodos para leer bytes del socket. El método run() es importante, ya que se ejecuta inmediatamente cuando se inicia el subproceso al final del ciclo:
@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(); }
Aquí el método sendProtocolVersion() envía una cadena RFB al cliente (visor VNC) y luego lee la cadena de versión del protocolo del cliente. El cliente debe responder con algo como "RFB 003.008\n". Por supuesto, el método readProtocolVersion() bloquea, como cualquier método cuyo nombre comience con la palabra read.
private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }
El método readProtocolVersion() es simple: lee 12 bytes del socket y devuelve un valor de cadena. La función readU8Array(int) lee el número especificado de bytes, en este caso 12 bytes. Si no hay suficientes bytes para leer en el socket, espera:
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; }
Similar a readU8Array(int) , existen métodos readU16int() y readU32int() que leen bytes del socket y devuelven un valor entero.
Después de enviar la versión del protocolo y leer la respuesta, el servicio RFB debe enviar un mensaje de seguridad:
/* * 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();
En esta implementación, se elige la forma más simple: no requiere ninguna contraseña del lado del cliente de VNC.
private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }
donde SECURITY_TYPE es una matriz de bytes:
private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};
Esta matriz de bytes por el protocolo RFB versión 3.3 significa que el visor VNC no necesita enviar ninguna contraseña.
A continuación, lo que el servicio RFB debe obtener del cliente es el indicador de escritorio compartido. Es un byte en el socket.
/* * 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();
Una vez que se lee el indicador de escritorio compartido desde el socket, lo ignoramos en nuestra implementación.
El servicio RFB tiene que enviar el mensaje de inicio del servidor:
/* * 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);
La clase JFrameMainWindow es JFrame, que está aquí con fines de demostración como fuente de gráficos. El mensaje de inicio del servidor tiene un ancho y alto de pantalla obligatorios en píxeles y un título de escritorio. En este ejemplo, es el título de JFrame obtenido por el método getTitle().
Después del mensaje de inicio del servidor, el subproceso del servicio RFB se repite leyendo desde el socket seis tipos de mensajes:
/* * 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); } }
Cada método readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() está bloqueando y activa alguna acción.

Por ejemplo, el método readClientCutText() lee el texto que está codificado en el mensaje cuando el usuario corta el texto en el lado del cliente y luego el visor VNC envía el texto a través del protocolo RFB al servidor. Luego, el texto se coloca en el lado del servidor en el Portapapeles.
Mensajes del cliente
Los seis mensajes deben ser compatibles con el servicio RFB, al menos a nivel de byte: cuando el cliente envía un mensaje, se debe leer un byte completo. Esto se debe a que el protocolo RFB está orientado a bytes y no hay límite entre dos mensajes.
El mensaje más importante es la solicitud de actualización del búfer de cuadros. El cliente puede solicitar una actualización completa o una actualización incremental de la pantalla.
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(); } }
El primer byte del mensaje de solicitud del búfer de trama es el tipo de mensaje. El valor es siempre 0x03. El siguiente byte es el indicador incremental, que le dice al servidor que envíe el fotograma completo o simplemente una diferencia. En caso de una solicitud de actualización completa, el servicio RFB tomará una captura de pantalla de la ventana principal usando la clase RobotScreen y la enviará al cliente.
Si se trata de una solicitud incremental, un indicador incrementalFrameBufferUpdate se establecerá en verdadero. Los componentes de Swing utilizarán esta bandera para verificar si necesitan enviar partes de la pantalla que han cambiado. Por lo general, JMenu, JMenuItem, JTextArea, etc. necesitan realizar una actualización incremental de la pantalla cuando el usuario mueve el puntero del mouse, hace clic, envía pulsaciones de teclas, etc.
El método sendFrameBufferUpdate(int, int, int, int, int[]) descarga el búfer de imagen en el zócalo.
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(); }
El método comprueba que las coordenadas (x, y) no se salgan de la pantalla junto con el ancho x alto del búfer de imagen. El valor del tipo de mensaje para la actualización del búfer de trama es 0x00. El valor de relleno suele ser 0x00 y el visor VNC debe ignorarlo. El número de rectángulos es un valor de dos bytes y define cuántos rectángulos siguen en el mensaje.
Cada rectángulo tiene una coordenada superior izquierda, ancho y alto, tipo de codificación y datos de píxeles. Hay algunos formatos de codificación eficientes que se pueden usar, como zrle, hextile y tight. Sin embargo, para mantener las cosas simples y fáciles de entender, usaremos codificación sin formato en nuestra implementación.
La codificación sin formato significa que el color del píxel se transmite como componente RGB. Si el cliente ha establecido la codificación de píxeles en 32 bits, se transmiten 4 bytes por cada píxel. Si el cliente usa el modo de color de 8 bits, cada píxel se transmite como 1 byte. El código se muestra en for-loop. Tenga en cuenta que para el modo de 8 bits, el mapa de color se usa para encontrar la mejor coincidencia para cada píxel de la captura de pantalla/búfer de imagen. Para el modo de píxeles de 32 bits, el búfer de imagen contiene una matriz de números enteros, cada valor tiene componentes RGB multiplexados.
Aplicación de demostración Swing
La aplicación de demostración Swing contiene un detector de acción que activa el método sendFrameBufferUpdate(int, int, int, int, int[]) . Por lo general, los elementos de la aplicación, como los componentes de Swing, deben tener oyentes y enviar cambios de pantalla al cliente. Por ejemplo, cuando el usuario escribe algo en JTextArea, debe transmitirse al visor 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; } } } }
El código de este oyente de acción es bastante simple: toma una captura de pantalla de la ventana principal JFrameMain usando la clase RobotScreen, luego se determina si se necesita una actualización parcial de la pantalla. La variable diffUpdateOfScreen se utiliza como indicador para la actualización parcial. Y finalmente, el búfer de imagen completo o solo se transmiten filas diferentes al cliente. Este código también considera más clientes conectados, es por eso que se usa el iterador y la lista de clientes se mantiene en el miembro RFBDemo.rfbClientList<RFBService> .
El oyente de acción de actualización de Framebuffer podría usarse en Timer, que puede iniciarse con cualquier cambio de JComponent:
/* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);
Este código está en el constructor de la clase JFrameMainWindow. El temporizador se inicia en el método doIncrementalFrameBufferUpdate():
public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }
Otros detectores de acciones suelen llamar al método doIncrementalFrameBufferUpdate():
public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }
Esta forma debe ser simple y fácil de seguir. Solo se necesita una referencia a la instancia de JFrameMainWindow y una sola llamada al método doIncrementalFrameBufferUpdate() . El método verificará si hay clientes conectados y, si los hay, se iniciará el temporizador timerUpdateFrameBuffer . Una vez que se inicia el temporizador, el oyente de acción tomará una captura de pantalla y se ejecutará sendFrameBufferUpdate() .
La figura anterior muestra la relación del oyente con el procedimiento de actualización del búfer de tramas. La mayoría de los oyentes se activan cuando el usuario realiza una acción: hace clic, selecciona texto, escribe algo en el área de texto, etc. Luego se ejecuta la función miembro doIncrementalFramebufferUpdate() que inicia el temporizador timerUpdateFrameBuffer . El temporizador eventualmente llamará al método sendFrameBufferUpdate() en la clase RFBService y hará que la pantalla se actualice en el lado del cliente (visor VNC).
Capturar pantalla, reproducir pulsaciones de teclas y mover el puntero del mouse en la pantalla
Java tiene una clase Robot incorporada que permite al desarrollador escribir una aplicación que tomará capturas de pantalla, enviará claves, manipulará el puntero del mouse, producirá clics, etc.
Para capturar el área de la pantalla donde se muestra la ventana JFrame, se utiliza RobotScreen. El método principal es getScreenshot(int, int, int, int) que captura una región de la pantalla. Los valores RGB para cada píxel se almacenan en una matriz 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; }
El método almacena píxeles en la matriz colorImageBuffer. Para obtener datos de píxeles, se puede utilizar el método getColorImageBuffer() .
El método también guarda el búfer de imagen anterior. Es posible obtener solo los píxeles que han sido modificados. Para obtener solo la diferencia del área de la imagen, use el método getDeltaImageBuffer() .
Enviar pulsaciones de teclas al sistema es fácil con la clase Robot. Sin embargo, algunos códigos clave especiales recibidos de los visores VNC deben traducirse correctamente primero. La clase RobotKeyboard tiene el método sendKey(int, int) que maneja teclas especiales y teclas alfanuméricas:
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); } }
El estado del argumento determina si la tecla se presiona o se suelta. Después de la traducción correcta del código de la tecla a la constante VT, el método doType(int, int) pasa el valor de la clave a Robot y el efecto es el mismo que el del usuario local cuando presionó la tecla en el teclado:
private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }
Similar a RobotKeyboard es la clase RobotMouse que maneja eventos de puntero y hace que el puntero del mouse se mueva y haga clic.
public void mouseMove(int x, int y) { robot.mouseMove(x, y); }
Las tres clases RobotScreen, RobotMouse y RobotKeyboard asignan una nueva instancia de Robot en el constructor:
this.robot = new Robot();
Solo tenemos una instancia de cada uno, ya que no es necesario en el nivel de la aplicación tener más de una instancia de la clase RobotScreen, RobotMouse o 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(); ... }
En esta aplicación de demostración, estas instancias se crean en la función main() .
El resultado es una aplicación basada en Swing en Java que actúa como un proveedor de servicios RFB y permite que los visores VNC estándar se conecten a ella:
Conclusión
El protocolo RFB es ampliamente utilizado y aceptado. Existen implementaciones de clientes en forma de visores VNC para casi todas las plataformas y dispositivos. El objetivo principal es mostrar escritorios de forma remota, pero también puede haber otras aplicaciones. Por ejemplo, podría crear ingeniosas herramientas gráficas y acceder a ellas de forma remota para mejorar sus flujos de trabajo remotos existentes.
Este artículo cubre los conceptos básicos del protocolo RFB, el formato del mensaje, cómo enviar parte de la pantalla y cómo manejar el teclado y el mouse. El código fuente completo con la aplicación de demostración Swing está disponible en GitHub.