Implementieren eines Remote-Framebuffer-Servers in Java

Veröffentlicht: 2022-03-11

Beim Computing ist Virtual Network Computing (VNC) ein grafisches Desktop-Sharing-System, das das Remote Framebuffer (RFB)-Protokoll verwendet, um einen anderen Computer fernzusteuern. Es überträgt Tastatur- und Mausereignisse von einem Computer zum anderen und leitet grafische Bildschirmaktualisierungen über ein Netzwerk in die andere Richtung zurück.

RFB ist ein einfaches Protokoll für den Fernzugriff auf grafische Benutzeroberflächen. Da es auf Frame-Buffer-Ebene arbeitet, ist es auf alle Windowing-Systeme und -Anwendungen anwendbar, einschließlich Microsoft Windows, Mac OS X und X Window System.

Erstellen einer serverseitigen Remote-Framebuffer-Protokoll-gestützten Swing-Anwendung in Java

Erstellen einer serverseitigen Remote-Framebuffer-Protokoll-gestützten Swing-Anwendung in Java
Twittern

In diesem Artikel werde ich zeigen, wie das serverseitige RFB-Protokoll implementiert wird, und mit einer kleinen Java-Swing-Anwendung demonstrieren, wie das Hauptfenster über eine TCP-Verbindung an VNC-Viewer übertragen wird. Die Idee ist, grundlegende Merkmale des Protokolls und eine mögliche Implementierung in Java zu demonstrieren.

Der Leser sollte über Grundkenntnisse der Programmiersprache Java verfügen und mit grundlegenden Konzepten von TCP/IP-Netzwerken, Client-Server-Modell usw. vertraut sein. Idealerweise ist der Leser ein Java-Entwickler und hat einige Erfahrung mit bekannten VNC-Implementierungen wie RealVNC , UltraVNC, TightVNC usw.

Spezifikation des Remote-Framebuffer-Protokolls

Die RFB-Protokollspezifikation ist ziemlich gut definiert. Laut Wikipedia hat das RFB-Protokoll mehrere Versionen. In diesem Artikel konzentrieren wir uns auf allgemeine Nachrichten, die von den meisten VNC-Implementierungen unabhängig von der Protokollversion richtig verstanden werden sollten.

Nachdem ein VNC-Viewer (Client) eine TCP-Verbindung zu einem VNC-Server (RFB-Dienst) aufgebaut hat, beinhaltet die erste Phase den Austausch der Protokollversion:

 RFB Service ----------- "RFB 003.003\n" -------> VNC viewer RFB Service <---------- "RFB 003.008\n" -------- VNC viewer

Es handelt sich um einen einfachen Bytestrom, der in ASCII-Zeichen dekodiert werden kann, z. B. „RFB 003.008\n“.

Sobald dies erledigt ist, ist der nächste Schritt die Authentifizierung. Der VNC-Server sendet ein Array von Bytes, um anzugeben, welche Art von Authentifizierungen er unterstützt. Zum Beispiel:

 RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer

Hier hat der VNC-Server nur 1 möglichen Authentifizierungstyp (0x02) gesendet. Das erste Byte 0x01 gibt die Anzahl der verfügbaren Authentifizierungstypen an. Der VNC-Viewer muss mit dem Wert 0x02 antworten, da dies der einzig mögliche Typ ist, der in diesem Beispiel vom Server unterstützt wird.

Als nächstes sendet der Server eine Authentifizierungsherausforderung (je nach Algorithmus gibt es mehrere), und der Client muss mit der richtigen Herausforderungsantwortnachricht antworten und warten, bis der Server die Antwort bestätigt. Sobald der Client authentifiziert ist, kann er mit dem Sitzungsaufbau fortfahren.

Der einfachste Weg hier ist, überhaupt keine Authentifizierung zu wählen. Das RFB-Protokoll ist ohnehin unsicher, unabhängig vom Authentifizierungsmechanismus. Wenn Sicherheit wichtig ist, besteht der richtige Weg darin, RFB-Sitzungen über VPN- oder SSH-Verbindungen zu tunneln.

An diesem Punkt sendet der VNC-Viewer eine Shared-Desktop-Nachricht, die angibt, ob der Client andere VNC-Viewer freigibt und ihnen erlaubt, sich mit demselben Desktop zu verbinden. Es liegt an der Implementierung des RFB-Dienstes, diese Nachricht zu berücksichtigen und möglicherweise zu verhindern, dass mehrere VNC-Zuschauer denselben Bildschirm teilen. Diese Nachricht ist nur 1 Byte lang und ein gültiger Wert ist entweder 0x00 oder 0x01.

Schließlich sendet der RFB-Server eine Server-Init-Nachricht, die Bildschirmgröße, Bits pro Pixel, Tiefe, Big-Endian-Flag und True-Color-Flag, Maximalwerte für rote, grüne und blaue Farben, Bitpositionen in Pixel für rote, grüne und blaue Farben enthält , und Desktop-String/Titel. Die ersten zwei Bytes stellen die Bildschirmbreite in Pixel dar, die nächsten zwei Bytes die Bildschirmhöhe. Nach Bildschirmhöhenbytes sollten Bits pro Pixelbyte in der Nachricht vorhanden sein. Der Wert ist normalerweise 8, 16 oder 32. Auf den meisten modernen Systemen mit vollem Farbbereich haben Bits pro Pixelbyte den Wert 32 (0x20). Es teilt dem Client mit, dass er vom Server Vollfarbe für jedes Pixel anfordern kann. Das Big-Endian-Byte ist nur dann ungleich Null, wenn Pixel in Big-Endian-Reihenfolge sind. Wenn das wahre Farbbyte nicht Null (wahr) ist, dann spezifizieren die nächsten sechs Bytes, wie rote, grüne und blaue Farbintensitäten aus dem Pixelwert zu extrahieren sind. Die nächsten sechs Bytes sind maximal zulässige Werte für die rote, grüne und blaue Komponente des Pixels. Dies ist wichtig im 8-Bit-Farbmodus, wo nur wenige Bits für jede Farbkomponente verfügbar sind. Rot-, Grün- und Blauverschiebungen bestimmen Bitpositionen für jede Farbe. Die letzten drei Bytes sind Auffüllzeichen und sollten vom Client ignoriert werden. Nach dem Pixelformat gibt es ein Byte, das die Länge einer Zeichenfolge für den Desktop-Titel definiert. Der Desktop-Titel ist eine ASCII-codierte Zeichenfolge in einem Byte-Array beliebiger Länge.

Remote-Framebuffer-Server-Client-Protokoll: Versionsaustausch, Authentifizierung und Server-Init-Meldung

Remote-Framebuffer-Server-Client-Protokoll: Versionsaustausch, Authentifizierung und Server-Init-Meldung
Twittern

Nach der Server-Init-Nachricht sollte der RFB-Dienst die Client-Nachrichten vom Socket lesen und decodieren. Es gibt 6 Arten von Nachrichten:

  • SetPixelFormat
  • SetEncodings
  • FramebufferUpdateRequest
  • Schlüsselereignis
  • PointerEvent
  • ClientCutText

Die Protokolldokumentation ist ziemlich genau und erklärt jede Nachricht. Für jede Nachricht wird jedes Byte erklärt. Beispiel: Server-Initialisierungsnachricht:

Anzahl Bytes Art Beschreibung
2 U16 Framebuffer-Breite
2 U16 Framebuffer-Höhe
16 PIXEL_FORMAT Server-Pixel-Format
4 U32 Namenslänge
Namenslänge U8-Array Namenskette

Hier ist PIXEL_FORMAT:

Anzahl Bytes Art Beschreibung
1 U8 Bits pro Pixel
1 U8 Tiefe
1 U8 Big-Endian-Flag
1 U8 True-Color-Flagge
2 U16 rot-max
2 U16 grün-max
2 U16 blau-max
1 U8 Rotverschiebung
1 U8 Grünverschiebung
1 U8 Blauverschiebung
3 Polsterung

U16 bedeutet vorzeichenlose 16-Bit-Ganzzahl (zwei Bytes), U32 ist vorzeichenlose 32-Bit-Ganzzahl, U8-Array ist ein Array von Bytes usw.

Protokollimplementierung in Java

Eine typische Java-Serveranwendung besteht aus einem Thread, der auf Client-Verbindungen wartet, und mehreren Threads, die Client-Verbindungen handhaben.

 /* * 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(); }

Hier wurde TCP-Port 5902 gewählt (Anzeige :2), und die While-Schleife wartet darauf, dass sich ein Client verbindet. Die Methode ServerSocket.accept() blockiert und lässt den Thread auf eine neue Client-Verbindung warten. Sobald der Client eine Verbindung herstellt, wird ein neuer Thread RFBService erstellt, der vom Client empfangene RFB-Protokollnachrichten verarbeitet.

Die Klasse RFBService implementiert die Runnable-Schnittstelle. Es ist voll von Methoden, um Bytes aus dem Socket zu lesen. Wichtig ist die Methode run() , die sofort ausgeführt wird, wenn der Thread am Ende der Schleife gestartet wird:

 @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(); }

Hier sendet die Methode sendProtocolVersion() den RFB-String an den Client (VNC-Viewer) und liest dann den Protokollversions-String vom Client. Der Client sollte mit etwas wie „RFB 003.008\n“ antworten. Die Methode readProtocolVersion() blockiert natürlich, wie jede Methode, deren Name mit dem Wort read beginnt.

 private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }

Die Methode readProtocolVersion() ist einfach: Sie liest 12 Bytes vom Socket und gibt einen String-Wert zurück. Die Funktion readU8Array(int) liest die angegebene Anzahl von Bytes, in diesem Fall 12 Bytes. Wenn auf dem Socket nicht genügend Bytes zum Lesen vorhanden sind, wartet es:

 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; }

Ähnlich wie bei readU8Array(int) existieren die Methoden readU16int() und readU32int() , die Bytes aus dem Socket lesen und einen ganzzahligen Wert zurückgeben.

Nach dem Senden der Protokollversion und dem Lesen der Antwort sollte der RFB-Dienst eine Sicherheitsnachricht senden:

 /* * 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();

In dieser Implementierung wird der einfachste Weg gewählt: kein Passwort von der VNC-Client-Seite verlangen.

 private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }

wobei SECURITY_TYPE ein Byte-Array ist:

 private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};

Dieses Byte-Array der RFB-Protokollversion 3.3 bedeutet, dass der VNC-Viewer kein Passwort senden muss.

Als Nächstes sollte der RFB-Dienst vom Client das Shared-Desktop-Flag erhalten. Es ist ein Byte auf 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();

Sobald das Shared-Desktop-Flag aus dem Socket gelesen wird, ignorieren wir es in unserer Implementierung.

Der RFB-Dienst muss eine Server-Init-Nachricht senden:

 /* * 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);

Die Klasse JFrameMainWindow ist JFrame, das hier zu Demozwecken als Grafikquelle dient. Die Server-Init-Nachricht hat eine obligatorische Bildschirmbreite und -höhe in Pixel und einen Desktop-Titel. In diesem Beispiel ist es der Titel von JFrame, der von der Methode getTitle() abgerufen wird.

Nach der Server-Init-Nachricht durchläuft der RFB-Service-Thread eine Schleife, indem er sechs Arten von Nachrichten aus dem Socket liest:

 /* * 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); } }

Jede Methode readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() blockiert und löst eine Aktion aus.

Beispielsweise liest die readClientCutText()- Methode Text, der in der Nachricht codiert ist, wenn der Benutzer Text auf der Client-Seite ausschneidet und der VNC-Viewer dann Text über das RFB-Protokoll an den Server sendet. Text wird dann serverseitig in der Zwischenablage abgelegt.

Client-Nachrichten

Alle sechs Nachrichten müssen vom RFB-Dienst unterstützt werden, zumindest auf Byte-Ebene: Wenn der Client eine Nachricht sendet, muss eine volle Bytelänge gelesen werden. Dies liegt daran, dass das RFB-Protokoll byteorientiert ist und es keine Grenze zwischen zwei Nachrichten gibt.

Die wichtigste Nachricht ist die Anforderung zur Aktualisierung des Rahmenpuffers. Der Kunde kann eine vollständige Aktualisierung oder eine inkrementelle Aktualisierung des Bildschirms anfordern.

 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(); } }

Das erste Byte der Rahmenpuffer-Anforderungsnachricht ist der Nachrichtentyp. Wert ist immer 0x03. Das nächste Byte ist ein inkrementelles Flag, das den Server anweist, einen vollständigen Frame oder nur eine Differenz zu senden. Im Falle einer vollständigen Aktualisierungsanforderung erstellt der RFB-Dienst mithilfe der RobotScreen-Klasse einen Screenshot des Hauptfensters und sendet ihn an den Client.

Wenn es sich um eine inkrementelle Anforderung handelt, wird ein Flag incrementalFrameBufferUpdate auf wahr gesetzt. Dieses Flag wird von Swing-Komponenten verwendet, um zu prüfen, ob sie Teile des Bildschirms senden müssen, die sich geändert haben. Normalerweise müssen JMenu, JMenuItem, JTextArea usw. den Bildschirm inkrementell aktualisieren, wenn der Benutzer den Mauszeiger bewegt, klickt, einen Tastendruck sendet usw.

Die Methode sendFrameBufferUpdate(int, int, int, int, int[]) leert den Bildpuffer in den Socket.

 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(); }

Die Methode prüft, ob die (x, y)-Koordinate nicht zusammen mit der Breite x Höhe des Bildpuffers vom Bildschirm verschwindet. Der Nachrichtentypwert für die Rahmenpufferaktualisierung ist 0x00. Der Füllwert ist normalerweise 0x00 und sollte vom VNC-Viewer ignoriert werden. Die Anzahl der Rechtecke ist ein Zwei-Byte-Wert und definiert, wie viele Rechtecke in der Nachricht folgen.

Jedes Rechteck hat eine obere linke Koordinate, Breite und Höhe, Codierungstyp und Pixeldaten. Es gibt einige effiziente Codierungsformate, die verwendet werden können, z. B. zrle, hextile und tight. Um die Dinge jedoch einfach und leicht verständlich zu halten, verwenden wir in unserer Implementierung eine Rohkodierung.

Rohcodierung bedeutet, dass die Pixelfarbe als RGB-Komponente übertragen wird. Wenn der Client die Pixelcodierung auf 32-Bit eingestellt hat, werden für jeden Pixel 4 Bytes übertragen. Wenn der Client den 8-Bit-Farbmodus verwendet, wird jedes Pixel als 1 Byte übertragen. Code wird in for-Schleife angezeigt. Beachten Sie, dass für den 8-Bit-Modus die Farbkarte verwendet wird, um die beste Übereinstimmung für jedes Pixel aus dem Screenshot-/Bildpuffer zu finden. Für den 32-Bit-Pixelmodus enthält der Bildpuffer ein Array von Ganzzahlen, jeder Wert hat gemultiplexte RGB-Komponenten.

Swing-Demo-Anwendung

Die Swing-Demoanwendung enthält einen Aktions-Listener, der die sendFrameBufferUpdate(int, int, int, int, int[])- Methode auslöst. Normalerweise sollten Anwendungselemente wie Swing-Komponenten Listener haben und Bildschirmänderungen an den Client senden. Wenn der Benutzer beispielsweise etwas in JTextArea eingibt, sollte es an den VNC-Viewer übertragen werden.

 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; } } } }

Der Code dieses Aktions-Listeners ist ziemlich einfach: Er macht einen Screenshot des Hauptfensters JFrameMain mit der RobotScreen-Klasse, dann wird bestimmt, ob eine teilweise Aktualisierung des Bildschirms erforderlich ist. Die Variable diffUpdateOfScreen wird als Flag für die partielle Aktualisierung verwendet. Und schließlich werden komplette Bildpuffer oder nur verschiedene Zeilen an den Client übertragen. Dieser Code berücksichtigt auch mehr verbundene Clients, deshalb wird der Iterator verwendet und die Clientliste wird im Element RFBDemo.rfbClientList<RFBService> gepflegt.

Framebuffer Update Action Listener könnte in Timer verwendet werden, der durch jede JComponent-Änderung gestartet werden kann:

 /* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);

Dieser Code befindet sich im Konstruktor der JFrameMainWindow-Klasse. Der Timer wird in der Methode doIncrementalFrameBufferUpdate() gestartet:

 public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }

Andere Aktions-Listener rufen normalerweise die Methode doIncrementalFrameBufferUpdate() auf:

 public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }

Dieser Weg sollte einfach und leicht zu befolgen sein. Es ist nur ein Verweis auf die JFrameMainWindow-Instanz und ein einzelner Aufruf der Methode doIncrementalFrameBufferUpdate() erforderlich . Die Methode prüft, ob Clients verbunden sind, und wenn ja, wird der Timer timerUpdateFrameBuffer gestartet. Sobald der Timer gestartet ist, macht der Aktions-Listener tatsächlich einen Screenshot und sendFrameBufferUpdate() wird ausgeführt.

Die obige Abbildung zeigt die Listener-Beziehung zum Rahmenpuffer-Aktualisierungsverfahren. Die meisten Listener werden ausgelöst, wenn der Benutzer eine Aktion ausführt: klickt, Text auswählt, etwas in den Textbereich eingibt usw. Dann wird die Member-Funktion doIncrementalFramebufferUpdate() ausgeführt, die den Timer timerUpdateFrameBuffer startet. Der Timer ruft schließlich die sendFrameBufferUpdate()- Methode in der RFBService-Klasse auf und bewirkt eine Bildschirmaktualisierung auf der Client-Seite (VNC-Viewer).

Erfassen Sie den Bildschirm, spielen Sie Tastenanschläge und bewegen Sie den Mauszeiger auf dem Bildschirm

Java hat eine eingebaute Robot-Klasse, die es Entwicklern ermöglicht, eine Anwendung zu schreiben, die Screenshots erstellt, Tasten sendet, den Mauszeiger manipuliert, Klicks erzeugt usw.

Um den Bereich des Bildschirms zu erfassen, in dem das JFrame-Fenster angezeigt wird, wird RobotScreen verwendet. Die Hauptmethode ist getScreenshot(int, int, int, int) , die einen Bereich des Bildschirms erfasst. RGB-Werte für jedes Pixel werden in einem int[]-Array gespeichert:

 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; }

Die Methode speichert Pixel im colorImageBuffer-Array. Um Pixeldaten zu erhalten, kann die Methode getColorImageBuffer() verwendet werden.

Die Methode speichert auch den vorherigen Bildpuffer. Es ist möglich, nur geänderte Pixel zu erhalten. Um nur die Differenz des Bildbereichs zu erhalten, verwenden Sie die Methode getDeltaImageBuffer() .

Das Senden von Tastenanschlägen an das System ist mit der Robot-Klasse einfach. Einige spezielle Tastencodes, die von VNC-Viewern empfangen werden, müssen jedoch zuerst korrekt übersetzt werden. Die Klasse RobotKeyboard hat die Methode sendKey(int, int) , die Sondertasten und alphanumerische Tasten behandelt:

 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); } }

Der Argumentstatus bestimmt, ob die Taste gedrückt oder losgelassen wird. Nach der korrekten Übersetzung des Tastencodes in die VT-Konstante übergibt die Methode doType(int, int) den Schlüsselwert an Robot und der Effekt ist der gleiche, als hätte der lokale Benutzer die Taste auf der Tastatur gedrückt:

 private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }

Ähnlich wie RobotKeyboard ist die RobotMouse-Klasse, die Zeigerereignisse verarbeitet und bewirkt, dass sich der Mauszeiger bewegt und klickt.

 public void mouseMove(int x, int y) { robot.mouseMove(x, y); }

Alle drei Klassen RobotScreen, RobotMouse und RobotKeyboard weisen eine neue Robot-Instanz im Konstruktor zu:

 this.robot = new Robot();

Wir haben jeweils nur eine Instanz, da auf Anwendungsebene nicht mehr als eine Instanz der Klasse RobotScreen, RobotMouse oder RobotKeyboard vorhanden sein muss.

 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(); ... }

In dieser Demoanwendung werden diese Instanzen in der Funktion main() erstellt.

Das Ergebnis ist eine Swing-basierte Anwendung in Java, die als RFB-Dienstanbieter fungiert und es standardmäßigen VNC-Viewern ermöglicht, sich damit zu verbinden:

Fazit

Das RFB-Protokoll ist weit verbreitet und akzeptiert. Client-Implementierungen in Form von VNC-Viewern existieren für fast alle Plattformen und Geräte. Der Hauptzweck besteht darin, Desktops aus der Ferne anzuzeigen, aber es kann auch andere Anwendungen geben. Sie könnten beispielsweise raffinierte grafische Tools erstellen und remote darauf zugreifen, um Ihre bestehenden Remote-Workflows zu verbessern.

Dieser Artikel behandelt die Grundlagen des RFB-Protokolls, das Nachrichtenformat, das Senden eines Teils des Bildschirms und den Umgang mit Tastatur und Maus. Der vollständige Quellcode mit der Swing-Demoanwendung ist auf GitHub verfügbar.