Implementarea unui server Framebuffer la distanță în Java

Publicat: 2022-03-11

În calcul, Virtual Network Computing (VNC) este un sistem grafic de partajare a desktopului care utilizează protocolul Remote Framebuffer (RFB) pentru a controla de la distanță un alt computer. Transmite evenimente de la tastatură și mouse de la un computer la altul și transmite actualizări grafice ale ecranului înapoi în cealaltă direcție printr-o rețea.

RFB este un protocol simplu pentru accesul de la distanță la interfețele grafice cu utilizatorul. Deoarece funcționează la nivelul tamponului de cadre, este aplicabil tuturor sistemelor și aplicațiilor de ferestre, inclusiv Microsoft Windows, Mac OS X și X Window System.

Construirea unei aplicații Swing bazate pe protocol Remote Framebuffer pe server în Java

Construirea unei aplicații Swing bazate pe protocol Remote Framebuffer pe server în Java
Tweet

În acest articol, voi arăta cum să implementez protocolul RFB pe partea de server și voi demonstra cu o mică aplicație Java Swing cum să transmiteți fereastra principală prin conexiunea TCP către vizualizatorii VNC. Ideea este de a demonstra caracteristicile de bază ale protocolului și posibila implementare în Java.

Cititorul ar trebui să aibă cunoștințe de bază despre limbajul de programare Java și ar trebui să fie familiarizat cu conceptele de bază ale rețelei TCP/IP, modelul client-server etc. În mod ideal, cititorul este un dezvoltator Java și are ceva experiență cu implementări VNC bine-cunoscute, cum ar fi RealVNC , UltraVNC, TightVNC etc.

Specificația protocolului Framebuffer la distanță

Specificația protocolului RFB este destul de bine definită. Conform Wikipedia, protocolul RFB are mai multe versiuni. Pentru acest articol, accentul nostru se va concentra pe mesajele comune care ar trebui să fie înțelese corect de majoritatea implementărilor VNC, indiferent de versiunea protocolului.

După ce un vizualizator VNC (client) stabilește o conexiune TCP la un server VNC (serviciu RFB), prima fază implică schimbul versiunii de protocol:

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

Este un simplu flux de octeți care pot fi decodați în caractere ASCII, cum ar fi „RFB 003.008\n”.

Odată ce este făcut, următorul pas este autentificarea. Serverul VNC trimite o serie de octeți pentru a indica ce tip de autentificare acceptă. De exemplu:

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

Aici serverul VNC a trimis doar 1 posibil tip de autentificare (0x02). Primul octet 0x01 indică numărul de tipuri de autentificare disponibile. Vizualizatorul VNC trebuie să răspundă cu valoarea 0x02, deoarece acesta este singurul tip posibil acceptat de server în acest exemplu.

Apoi, serverul va trimite provocarea de autentificare (în funcție de algoritmul, există mai mulți), iar clientul trebuie să răspundă cu un mesaj de răspuns la provocare adecvat și să aștepte ca serverul să confirme răspunsul. Odată ce clientul este autentificat, acesta poate continua procesul de stabilire a sesiunii.

Cel mai simplu mod aici este să nu alegeți deloc autentificarea. Protocolul RFB este oricum nesigur, indiferent de mecanismul de autentificare. Dacă securitatea este importantă, modalitatea corectă ar fi să se tunelească sesiunile RFB prin conexiuni VPN sau SSH.

În acest moment, vizualizatorul VNC trimite un mesaj desktop partajat care spune dacă clientul va partaja și va permite altor vizualizatori VNC să se conecteze la același desktop. Depinde de implementarea serviciului RFB să ia în considerare acel mesaj și, eventual, să împiedice mai mulți vizualizatori VNC să partajeze același ecran. Acest mesaj are o lungime de numai 1 octet, iar o valoare validă este fie 0x00, fie 0x01.

În cele din urmă, serverul RFB trimite un mesaj de inițializare a serverului, care conține dimensiunea ecranului, biți per pixel, adâncime, steag big endian și steaguri de culoare adevărată, valori maxime pentru culorile roșu, verde și albastru, pozițiile biților în pixeli pentru culorile roșu, verde și albastru. , și șir/titlu pentru desktop. Primii doi octeți reprezintă lățimea ecranului în pixeli, următorii doi octeți reprezintă înălțimea ecranului. După octeții de înălțime a ecranului, în mesaj ar trebui să fie prezenți biți pe octet de pixel. Valoarea este de obicei 8, 16 sau 32. Pe majoritatea sistemelor moderne cu gamă completă de culori, biții per octet de pixel au valoarea 32 (0x20). Îi spune clientului că poate solicita culoarea completă pentru fiecare pixel de la server. Octetul Big Endian este diferit de zero numai dacă pixelii sunt în ordinea Big Endian. Dacă octetul de culoare adevărată este diferit de zero (adevărat), atunci următorii șase octeți specifică cum să extragă intensitățile de culoare roșie, verde și albastră din valoarea pixelului. Următorii șase octeți sunt valorile maxime permise pentru componenta roșie, verde și albastră a pixelului. Acest lucru este important în modul de culoare pe 8 biți, unde doar câțiva biți sunt disponibili pentru fiecare componentă de culoare. Schimbările roșu, verde și albastru determină pozițiile biților pentru fiecare culoare. Ultimii trei octeți sunt umpluți și ar trebui ignorați de client. După formatul pixelilor, există un octet care definește lungimea unui șir pentru titlul desktopului. Titlul desktop este un șir codificat ASCII într-o matrice de octeți de lungime arbitrară.

Protocolul server-client Framebuffer la distanță: schimb de versiuni, autentificare și mesaj de inițiere a serverului

Protocolul server-client Framebuffer la distanță: schimb de versiuni, autentificare și mesaj de inițiere a serverului
Tweet

După mesajul de inițializare a serverului, serviciul RFB ar trebui să citească mesajele client de pe soclu și să le decodeze. Există 6 tipuri de mesaje:

  • SetPixelFormat
  • SetEncodings
  • FramebufferUpdateRequest
  • KeyEvent
  • PointerEvent
  • ClientCutText

Documentația protocolului este destul de exactă și explică fiecare mesaj. Pentru fiecare mesaj, fiecare octet este explicat. De exemplu, mesajul de pornire al serverului:

Numărul de octeți Tip Descriere
2 U16 framebuffer-lățime
2 U16 framebuffer-inaltime
16 PIXEL_FORMAT format-server-pixel
4 U32 lungimea numelui
lungimea numelui matrice U8 șir de nume

Aici, PIXEL_FORMAT este:

Numărul de octeți Tip Descriere
1 U8 biți-pe-pixel
1 U8 adâncime
1 U8 steagul-big-endian
1 U8 steag de culoare adevărată
2 U16 roșu-max
2 U16 verde-max
2 U16 albastru-max
1 U8 tura roșie
1 U8 green-shift
1 U8 Schimbare albastră
3 căptușeală

U16 înseamnă un întreg nesemnat de 16 biți (doi octeți), U32 este un întreg nesemnat de 32 de biți, matricea U8 este o matrice de octeți etc.

Implementarea protocolului în Java

O aplicație tipică de server Java constă dintr-un fir care ascultă conexiunile client și mai multe fire care gestionează conexiunile client.

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

Aici a fost ales portul TCP 5902 (afișare :2), iar bucla while așteaptă să se conecteze un client. Metoda ServerSocket.accept() se blochează și face firul să aștepte o nouă conexiune client. Odată ce clientul se conectează, este creat un nou thread RFBService care gestionează mesajele de protocol RFB primite de la client.

Clasa RFBService implementează interfața Runnable. Este plin de metode de a citi octeții din socket. Este importantă metoda run() , care este executată imediat când firul de execuție este pornit la sfârșitul buclei:

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

Aici metoda sendProtocolVersion() trimite șirul RFB către client (vizualizator VNC) și apoi citește șirul versiunii de protocol de la client. Clientul ar trebui să răspundă cu ceva de genul „RFB 003.008\n”. Metoda readProtocolVersion() este desigur blocantă, ca orice metodă al cărei nume începe cu cuvântul read.

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

Metoda readProtocolVersion() este simplă: citește 12 octeți din socket și returnează o valoare șir. Funcția readU8Array(int) citește numărul specificat de octeți, în acest caz 12 octeți. Dacă nu există destui octeți pentru a citi pe socket, acesta așteaptă:

 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 cu readU8Array(int) , există metode readU16int() și readU32int() care citesc octeți din socket și returnează valoare întreagă.

După trimiterea versiunii de protocol și citirea răspunsului, serviciul RFB ar trebui să trimită un mesaj de securitate:

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

În această implementare, se alege cea mai simplă modalitate: nu aveți nevoie de nicio parolă din partea clientului VNC.

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

unde SECURITY_TYPE este o matrice de octeți:

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

Această serie de octeți conform protocolului RFB versiunea 3.3 înseamnă că vizualizatorul VNC nu trebuie să trimită nicio parolă.

În continuare, serviciul RFB ar trebui să primească de la client este flagul desktop partajat. Este un octet pe soclu.

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

Odată ce indicatorul desktop partajat este citit de pe socket, îl ignorăm în implementarea noastră.

Serviciul RFB trebuie să trimită un mesaj de inițializare a serverului:

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

Clasa JFrameMainWindow este JFrame, care este aici pentru scop demonstrativ ca sursă de grafică. Mesajul de inițializare a serverului are lățimea și înălțimea ecranului obligatorii în pixeli și titlul desktopului. În acest exemplu, este titlul lui JFrame obținut prin metoda getTitle().

După mesajul de inițializare a serverului, firul de execuție al serviciului RFB trece prin citirea din soclu a șase tipuri de mesaje:

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

Fiecare metodă readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() blochează și declanșează anumite acțiuni.

De exemplu, metoda readClientCutText() citește textul care este codificat în mesaj atunci când utilizatorul taie text pe partea clientului și apoi vizualizatorul VNC trimite text prin protocolul RFB către server. Textul este apoi plasat pe partea serverului în Clipboard.

Mesaje ale clientului

Toate cele șase mesaje trebuie să fie suportate de serviciul RFB, cel puțin la nivel de octeți: atunci când clientul trimite mesaj, trebuie citit o lungime completă de octeți. Acest lucru se datorează faptului că protocolul RFB este orientat pe octeți și nu există o limită între două mesaje.

Cel mai mare mesaj de import este cererea de actualizare a memoriei tampon. Clientul poate solicita actualizarea completă sau actualizarea incrementală a ecranului.

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

Primul octet al mesajului de solicitare a memoriei tampon de cadre este tipul mesajului. Valoarea este întotdeauna 0x03. Următorul octet este indicatorul incremental, care îi spune serverului să trimită cadru complet sau doar o diferență. În cazul unei solicitări de actualizare completă, serviciul RFB va face o captură de ecran a ferestrei principale folosind clasa RobotScreen și o va trimite clientului.

Dacă este o solicitare incrementală, indicatorul incrementalFrameBufferUpdate va fi setat la true. Acest steag va fi folosit de componentele Swing pentru a verifica dacă trebuie să trimită părți ale ecranului care s-au schimbat. De obicei, JMenu, JMenuItem, JTextArea etc. trebuie să facă o actualizare incrementală a ecranului atunci când utilizatorul mută cursorul mouse-ului, face clic, trimite apăsarea tastei etc.

Metoda sendFrameBufferUpdate(int, int, int, int, int[]) șterge memoria tampon de imagine în 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(); }

Metoda verifică dacă coordonatele (x, y) nu ies de pe ecran împreună cu lățimea x înălțimea memoriei tampon de imagine. Valoarea tipului de mesaj pentru actualizarea cadru tampon este 0x00. Valoarea de completare este de obicei 0x00 și ar trebui ignorată de vizualizatorul VNC. Numărul de dreptunghiuri este valoarea de doi octeți și definește câte dreptunghiuri urmează în mesaj.

Fiecare dreptunghi are coordonatele din stânga sus, lățimea și înălțimea, tipul de codificare și datele pixelilor. Există câteva formate de codare eficiente care pot fi utilizate, cum ar fi zrle, hextile și tight. Cu toate acestea, pentru a menține lucrurile simple și ușor de înțeles, vom folosi codificarea brută în implementarea noastră.

Codarea brută înseamnă că culoarea pixelilor este transmisă ca componentă RGB. Dacă clientul a setat codificarea pixelilor la 32 de biți, atunci se transmit 4 octeți pentru fiecare pixel. Dacă clientul folosește modul de culoare pe 8 biți, atunci fiecare pixel este transmis ca 1 octet. Codul este afișat în bucla for. Rețineți că, pentru modul pe 8 biți, harta de culori este utilizată pentru a găsi cea mai bună potrivire pentru fiecare pixel din captură de ecran / tampon de imagine. Pentru modul pixeli pe 32 de biți, memoria tampon de imagine conține o serie de numere întregi, fiecare valoare având componente RGB multiplexate.

Aplicație Swing Demo

Aplicația demo Swing conține un ascultător de acțiuni care declanșează metoda sendFrameBufferUpdate(int, int, int, int, int[]) . De obicei, elementele aplicației, cum ar fi componentele Swing, ar trebui să aibă ascultători și să trimită modificarea ecranului către client. De exemplu, atunci când utilizatorul tasta ceva în JTextArea, acesta ar trebui să fie transmis la vizualizatorul 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; } } } }

Codul acestui ascultător de acțiuni este destul de simplu: ia o captură de ecran a ferestrei principale JFrameMain folosind clasa RobotScreen, apoi se stabilește dacă este necesară o actualizare parțială a ecranului. Variabila diffUpdateOfScreen este folosită ca semnalizare pentru actualizarea parțială. Și, în sfârșit, tampon de imagine complet sau doar rânduri diferite sunt transmise clientului. Acest cod ia în considerare, de asemenea, mai mulți clienți conectați, de aceea este utilizat iteratorul și lista de clienți este menținută în membrul RFBDemo.rfbClientList<RFBService> .

Ascultătorul de acțiuni de actualizare Framebuffer poate fi utilizat în Timer, care poate fi pornit de orice modificare JComponent:

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

Acest cod este în constructorul clasei JFrameMainWindow. Cronometrul este pornit în metoda doIncrementalFrameBufferUpdate():

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

Alți ascultători de acțiuni apelează de obicei metoda doIncrementalFrameBufferUpdate():

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

Acest mod ar trebui să fie simplu și ușor de urmat. Este necesară doar o referință la instanța JFrameMainWindow și un singur apel al metodei doIncrementalFrameBufferUpdate() . Metoda va verifica dacă există clienți conectați și, dacă există, timer -ul timerUpdateFrameBuffer va fi pornit. Odată pornit temporizatorul, ascultătorul de acțiuni va face de fapt o captură de ecran și va fi executat sendFrameBufferUpdate() .

Figura de mai sus arată relația ascultatorului cu procedura de actualizare a memoriei tampon. Cei mai mulți ascultători sunt declanșați atunci când utilizatorul face acțiuni: face clic, selectează text, tastează ceva în zona de text etc. Apoi este executată funcția membru doIncrementalFramebufferUpdate() care pornește timerUpdateFrameBuffer . Cronometrul va apela în cele din urmă metoda sendFrameBufferUpdate() în clasa RFBService și va provoca actualizarea ecranului pe partea client (vizualizator VNC).

Capturați ecranul, redați apăsările de la taste și mutați indicatorul mouse-ului pe ecran

Java are o clasă Robot încorporată care permite dezvoltatorului să scrie o aplicație care va prelua capturi de ecran, va trimite chei, va manipula cursorul mouse-ului, va produce clicuri etc.

Pentru a prelua zona de ecran în care este afișată fereastra JFrame, se folosește RobotScreen. Metoda principală este getScreenshot(int, int, int, int) care captează o regiune a ecranului. Valorile RGB pentru fiecare pixel sunt stocate într-o matrice 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; }

Metoda stochează pixeli în matricea colorImageBuffer. Pentru a obține date de pixeli, se poate folosi metoda getColorImageBuffer() .

Metoda salvează, de asemenea, memoria tampon de imagine anterioară. Este posibil să obțineți numai pixeli care au fost modificați. Pentru a obține doar diferența de zonă a imaginii, utilizați metoda getDeltaImageBuffer() .

Trimiterea tastelor la sistem este ușoară cu clasa Robot. Cu toate acestea, unele coduri speciale primite de la telespectatorii VNC trebuie mai întâi traduse corect. Clasa RobotKeyboard are metoda sendKey(int, int) care gestionează tastele speciale și cheile alfanumerice:

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

Starea argumentului determină dacă tasta este apăsată sau eliberată. După traducerea corectă a codului tastei în constanta VT, metoda doType(int, int) transmite valoarea cheii către Robot și efectul este același ca utilizatorul local a apăsat tasta de pe tastatură:

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

Similar cu RobotKeyboard este clasa RobotMouse care se ocupă de evenimentele pointer și face ca indicatorul mouse-ului să se miște și să facă clic.

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

Toate cele trei clase RobotScreen, RobotMouse și RobotKeyboard alocă o nouă instanță Robot în constructor:

 this.robot = new Robot();

Avem doar o instanță din fiecare, deoarece nu este nevoie la nivel de aplicație de a avea mai mult de o instanță a clasei RobotScreen, RobotMouse sau 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(); ... }

În această aplicație demonstrativă, aceste instanțe sunt create în funcția main() .

Rezultatul este o aplicație bazată pe Swing în Java, care acționează ca un furnizor de servicii RFB și permite vizualizatorilor VNC standard să se conecteze la ea:

Concluzie

Protocolul RFB este utilizat și acceptat pe scară largă. Implementări client sub formă de vizualizatoare VNC există pentru aproape toate platformele și dispozitivele. Scopul principal este afișarea de la distanță a desktop-urilor, dar pot exista și alte aplicații. De exemplu, puteți crea instrumente grafice ingenioase și le puteți accesa de la distanță pentru a vă îmbunătăți fluxurile de lucru existente la distanță.

Acest articol acoperă elementele de bază ale protocolului RFB, formatul mesajului, cum să trimiteți o parte a ecranului și cum să faceți față cu tastatura și mouse-ul. Codul sursă complet cu aplicația demo Swing este disponibil pe GitHub.