Laskennassa Virtual Network Computing (VNC) on graafinen työpöydän jakamisjärjestelmä, joka käyttää Remote Framebuffer (RFB) -protokollaa toisen tietokoneen etähallintaan. Se lähettää näppäimistö- ja hiirtapahtumat tietokoneelta toiselle ja välittää graafisen näytön päivitykset takaisin toiseen suuntaan verkon kautta.
RFB on yksinkertainen protokolla graafisen käyttöliittymän etäkäyttöön. Koska se toimii kehyspuskuritasolla, sitä voidaan käyttää kaikissa ikkunointijärjestelmissä ja -sovelluksissa, mukaan lukien Microsoft Windows, Mac OS X ja X Window System.
Tässä artikkelissa näytän, kuinka RFB-palvelinpuolen protokolla otetaan käyttöön, ja esitän pienellä Java Swing -sovelluksella, kuinka pääikkuna lähetetään TCP-yhteyden kautta VNC-katsojille. Ajatuksena on esitellä protokollan perusominaisuudet ja mahdollinen toteutus Java: ssa.
Lukijalla tulee olla perustiedot Java-ohjelmointikielestä ja hänen on tunnettava TCP / IP-verkostoitumisen peruskäsitteet, asiakas-palvelin-malli jne. Ihannetapauksessa lukija on Java-kehittäjä ja sillä on jonkin verran kokemusta tunnetuista VNC-toteutuksista, kuten RealVNC, UltraVNC, TightVNC jne.
RFB-protokollan määrittely on kaunis hyvin määritelty . Wikipedian mukaan RFB-protokollalla on useita versioita. Tässä artikkelissa keskitymme yleisiin viesteihin, jotka useimpien VNC-toteutusten tulisi ymmärtää oikein protokollaversiosta riippumatta.
Kun VNC-katsoja (asiakas) on muodostanut TCP-yhteyden VNC-palvelimeen (RFB-palvelu), ensimmäiseen vaiheeseen liittyy protokollaversio:
RFB Service ----------- 'RFB 003.003
' -------> VNC viewer RFB Service <---------- 'RFB 003.008
' -------- VNC viewer
Se on yksinkertainen tavuvirta, joka voidaan purkaa ASCII-merkit , kuten “RFB 003.008 n”.
Kun se on tehty, seuraava vaihe on todennus. VNC-palvelin lähettää joukon tavuja osoittamaan, minkä tyyppisiä todennuksia se tukee. Esimerkiksi:
RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer
Tässä VNC-palvelin lähetti vain yhden mahdollisen todennustyypin (0x02). Ensimmäinen tavu 0x01 tarkoittaa käytettävissä olevien todennustyyppien määrää. VNC-katsojan on vastattava arvolla 0x02, koska se on ainoa mahdollinen tyyppi, jota palvelin tukee tässä esimerkissä.
Seuraavaksi palvelin lähettää todennushaasteen (riippuen siitä, mitä algoritmia on useita), ja asiakkaan on vastattava asianmukaisella haastevastausviestillä ja odotettava palvelimen vahvistavan vastauksen. Kun asiakas on todennettu, hän voi jatkaa istunnon perustamista.
Yksinkertaisin tapa tässä on valita, ettei todentamista ole lainkaan. RFB-protokolla on joka tapauksessa epävarma, todennusmekanismista riippumatta. Jos turvallisuus on tärkeää, oikea tapa olisi tunneli RFB-istunnot VPN- tai SSH-yhteyksien kautta.
Tässä vaiheessa VNC-katsoja lähettää jaetun työpöydän viestin, joka kertoo, jakaakö asiakas asiakas ja sallivatko muiden VNC-katsojien muodostaa yhteyden samalle työpöydälle. RFB-palvelun toteutus on harkita tätä viestiä ja ehkä estää useita VNC-katsojia jakamasta samaa näyttöä. Tämän viestin pituus on vain 1 tavu, ja kelvollinen arvo on joko 0x00 tai 0x01.
Lopuksi RFB-palvelin lähettää palvelimen init-viestin, joka sisältää näytön mitat, bittiä pikseliä kohden, syvyyden, ison endian-lipun ja todelliset väriliput, punaisen, vihreän ja sinisen värin maksimiarvot, bittiasennot pikselissä punaiselle, vihreälle ja siniselle värille ja työpöydän merkkijono / otsikko. Ensimmäiset kaksi tavua edustavat näytön leveyttä pikseleinä, seuraavat kaksi tavua ovat näytön korkeutta. Näytön korkeuden tavujen jälkeen sanomassa tulisi olla bittejä pikselitavua kohti. Arvo on yleensä 8, 16 tai 32. Useimmissa nykyaikaisissa järjestelmissä, joissa on koko värialue, bitteillä pikselitavulla on arvo 32 (0x20). Se kertoo asiakkaalle, että se voi pyytää täysväriä jokaiselle pikselille palvelimelta. Big endian tavu ei ole nolla vain, jos pikselit ovat suuressa endian järjestyksessä. Jos todellinen väritavu ei ole nolla (tosi), seuraavat kuusi tavua määrittelevät, kuinka puna-, vihreän ja sinisen värin intensiteetit erotetaan pikseliarvosta. Seuraavat kuusi tavua ovat pikselin punaisen, vihreän ja sinisen komponentin suurimmat sallitut arvot. Tämä on tärkeää 8-bittisessä väritilassa, jossa kullekin värikomponentille on saatavana vain vähän bittejä. Punainen, vihreä ja sininen siirtymä määräävät bittiasennot jokaiselle värille. Kolme viimeistä tavua ovat täytteitä, ja asiakkaan tulisi jättää ne huomioimatta. Pikselimuodon jälkeen on tavu, joka määrittelee työpöydän otsikon merkkijonon pituuden. Työpöydän otsikko on ASCII-koodattu merkkijono mielivaltaisessa tavussa.
Palvelimen aloitussanoman jälkeen RFB-palvelun tulisi lukea asiakasviestit pistorasiasta ja purkaa ne. Viestejä on 6 tyyppiä:
Protokollan dokumentaatio on melko tarkka ja selittää jokaisen viestin. Jokaiselle viestille selitetään joka tavu. Esimerkiksi palvelimen init-viesti:
Tavujen määrä | Tyyppi | Kuvaus |
---|---|---|
2 | U16 | kehyspuskurin leveys |
2 | U16 | kehyspuskurin korkeus |
16 | PIXEL_FORMAT | palvelin-pikselimuoto |
4 | U32 | nimen pituus |
nimen pituus | U8-taulukko | nimi-merkkijono |
Tässä PIXEL_FORMAT on:
Tavujen määrä | Tyyppi | Kuvaus |
---|---|---|
yksi | U8 | bittiä / pikseli |
yksi | U8 | syvyys |
yksi | U8 | iso-endian-lippu |
yksi | U8 | true-color-flag |
2 | U16 | punainen-max |
2 | U16 | vihreä-max |
2 | U16 | sininen-max |
yksi | U8 | punainen muutos |
yksi | U8 | vihreä muutos |
yksi | U8 | sininen vuoro |
3 | pehmuste |
U16 tarkoittaa allekirjoittamatonta 16-bittistä kokonaislukua (kaksi tavua), U32 on allekirjoittamatonta 32-bittistä kokonaislukua, U8-taulukko on joukko tavuja jne.
Tyypillinen Java-palvelinsovellus koostuu yhdestä säikeestä kuuntelemaan asiakasyhteyksiä ja useista säikeistä, jotka käsittelevät asiakasyhteyksiä.
/* * 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(); }
Tässä valittiin TCP-portti 5902 (näyttö: 2), ja while-silmukka odottaa asiakkaan muodostavan yhteyden. Menetelmä ServerSocket.accept () estää ja se saa langan odottamaan uutta asiakasyhteyttä. Kun asiakas on muodostanut yhteyden, luodaan uusi säie RFBService, joka käsittelee asiakkaalta vastaanotetut RFB-protokollaviestit.
Luokan RFBService toteuttaa Runnable-käyttöliittymän. Se on täynnä menetelmiä tavujen lukemiseen pistorasiasta. Menetelmä juosta() on tärkeä, joka suoritetaan heti, kun ketju aloitetaan silmukan lopussa:
@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(); }
Tässä menetelmä sendProtocolVersion () lähettää RFB-merkkijonon asiakkaalle (VNC-katseluohjelma) ja sitten lukee protokollaversiomerkkijonon asiakkaalta. Asiakkaan tulee vastata esimerkiksi nimellä 'RFB 003.008 n'. Menetelmä readProtocolVersion () on tietysti esto, kuten mikä tahansa menetelmä, jonka nimi alkaa sanalla luettu.
private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }
Method readProtocolVersion () on yksinkertainen: se lukee 12 tavua pistorasiasta ja palauttaa merkkijonon arvon. Funktio readU8Array (int) lukee määrätyn määrän tavuja, tässä tapauksessa 12 tavua. Jos tavussa ei ole tarpeeksi tavuja lukemista varten, se odottaa:
private byte[] readU8Array(int len) throws IOException { byte[] buffer = new byte[len]; int offset = 0, left = buffer.length; while (offset Samanlainen kuin readU8Array (int) , menetelmät lukuU16int () ja readU32int () jotka lukevat tavuja pistorasiasta ja palauttavat kokonaisluvun.
Lähetettyään protokolla-version ja lukenut vastauksen, RFB-palvelun tulee lähettää suojausviesti:
/* * 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();
Tässä toteutuksessa valitaan yksinkertaisin tapa: älä vaadi salasanaa VNC-asiakaspuolelta.
private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }
missä SECURITY_TYPE on tavutaulukko:
private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};
Tämä tavujoukko RFB-protokollaversiolla 3.3 tarkoittaa, että VNC-katsojan ei tarvitse lähettää mitään salasanaa.
Seuraavaksi mitä RFB-palvelun pitäisi saada asiakkaalta, on jaettu työpöydän lippu. Se on yksi tavu pistorasiassa.
/* * 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();
Kun jaettu työpöydän lippu on luettu pistorasiasta, jätämme sen huomioimatta toteutuksessamme.
RFB-palvelun on lähetettävä palvelimen init-viesti:
/* * 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);
Luokka JFrameMainWindow on JFrame, joka on täällä demotarkoituksessa grafiikan lähteenä. Palvelimen aloitussanomassa on pakollinen näytön leveys ja korkeus pikseleinä ja työpöydän otsikko. Tässä esimerkissä se on JFrame-otsikko, joka on saatu getTitle () -menetelmällä.
Palvelimen aloitussanoman jälkeen RFB-palveluketju silmukkaa lukemalla kannasta kuusi viestityyppiä:
/* * 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); } }
Jokainen menetelmä readSetPixelFormat () , readSetEncoding () , readFrameBufferUpdateRequest () , ... readClientCutText () estää ja laukaisee jonkin toiminnon.
Esimerkiksi, readClientCutText () method lukee viestissä koodatun tekstin, kun käyttäjä leikkaa tekstiä asiakaspuolelta ja sitten VNC-katsoja lähettää tekstiä RFB-protokollan kautta palvelimelle. Sitten teksti sijoitetaan palvelinpuolelle leikepöydälle.
Asiakasviestit
RFB-palvelun on tuettava kaikkia kuutta viestiä, ainakin tavutasolla: kun asiakas lähettää viestin, on luettava koko tavu. Tämä johtuu siitä, että RFB-protokolla on tavuorientoitu eikä kahden viestin välillä ole rajaa.
Tärkein viesti on kehyspuskurin päivityspyyntö. Asiakas voi pyytää näytön täydellistä päivitystä tai osittaista päivitystä.
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(); } }
Kehyspuskuripyyntösanoman ensimmäinen tavu on sanomatyyppi. Arvo on aina 0x03. Seuraava tavu on inkrementaalilippu, joka käskee palvelinta lähettämään koko kehyksen tai vain eron. Täyden päivityspyynnön yhteydessä RFB-palvelu ottaa kuvakaappauksen pääikkunasta RobotScreen-luokan avulla ja lähettää sen asiakkaalle.
Jos se on inkrementaalinen pyyntö, lippu incrementalFrameBufferUpdate asetetaan tosi. Swing-komponentit käyttävät tätä lippua tarkistaakseen, tarvitseeko heidän lähettää muutettuja näytön osia. Yleensä JMenun, JMenuItemin, JTextArean jne. Täytyy päivittää näyttö vähitellen, kun käyttäjä siirtää hiiren osoitinta, napsauttaa, lähettää näppäimistön jne.
Menetelmä sendFrameBufferUpdate (int, int, int, int, int []) huuhtelee kuvapuskurin liitäntään.
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(); }
Menetelmä tarkistaa, että (x, y) -koordinaatit eivät mene pois näytöltä yhdessä kuvapuskurin leveyden x korkeuden kanssa. Kehyspuskurin päivityksen viestityypin arvo on 0x00. Täyte-arvo on yleensä 0x00, ja VNC-katsoja ei ota sitä huomioon. Suorakulmioiden määrä on kahden tavun arvo ja määrittää kuinka monta suorakulmiota seuraa viestiä.
Jokaisessa suorakulmiossa on vasen yläkoordinaatti, leveys ja korkeus, koodaustyyppi ja pikselidata. On olemassa joitain tehokkaita koodausmuotoja, joita voidaan käyttää, kuten zrle, hekstiili ja tiukka. Pitäen kuitenkin asiat yksinkertaisina ja helposti ymmärrettävinä käytämme raakakoodausta toteutuksessamme.
Raakakoodaus tarkoittaa, että pikseliväri lähetetään RGB-komponenttina. Jos asiakas on asettanut pikselikoodauksen 32-bittiseksi, lähetetään 4 tavua jokaiselle pikselille. Jos asiakas käyttää 8-bittistä väritilaa, kukin pikseli lähetetään 1 tavuna. Koodi näkyy for-loop. Huomaa, että 8-bittisessä tilassa värikarttaa käytetään parhaan vastaavuuden löytämiseen kullekin pikselille kuvakaappauksesta / kuvapuskurista. 32-bittisessä pikselitilassa kuvapuskuri sisältää joukon kokonaislukuja, jokaisessa arvossa on RGB-komponentit multipleksoitu.
Swing-demosovellus
Swing-demosovellus sisältää toimintojen kuuntelijan, joka laukaisee sendFrameBufferUpdate (int, int, int, int, int []) menetelmä. Yleensä sovelluselementeillä, kuten Swing-komponenteilla, pitäisi olla kuuntelijoita ja lähettää näytön muutos asiakkaalle. Esimerkiksi kun käyttäjä kirjoittaa jotain JTextArea-sovellukseen, se on lähetettävä VNC-katseluohjelmaan.
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 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 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; } } } }
Tämän toimintakuuntelijan koodi on melko yksinkertainen: se ottaa kuvakaappauksen JFrameMain-pääikkunasta RobotScreen-luokan avulla, sitten määritetään, tarvitaanko näytön osittaista päivitystä. Vaihteleva diffUpdateOfScreen käytetään lippuna osittaiselle päivitykselle. Ja lopuksi täydellinen kuvapuskuri tai vain eri rivit lähetetään asiakkaalle. Tämä koodi pitää myös useampia asiakkaita yhdistettyinä, minkä vuoksi iteraattoria käytetään ja asiakasluetteloa ylläpidetään RFBDemo.rfbClientList jäsen.
Framebuffer-päivitystoimintojen kuuntelijaa voidaan käyttää ajastimessa, joka voidaan käynnistää millä tahansa JComponent-muutoksella:
/* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);
Tämä koodi on JFrameMainWindow-luokan konstruktorissa. Ajastin käynnistetään doIncrementalFrameBufferUpdate () -menetelmässä:
public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }
Muut kuuntelijat kutsuvat yleensä doIncrementalFrameBufferUpdate () -menetelmää:
public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }
Tämän tavan tulisi olla yksinkertainen ja helppo seurata. Tarvitaan vain viite JFrameMainWindow-ilmentymään ja yksi kutsu doIncrementalFrameBufferUpdate () menetelmä. Method tarkistaa, onko asiakkaita kytketty, ja jos on, ajastin timerUpdateFrameBuffer käynnistetään. Kun ajastin on käynnistetty, toiminnan kuuntelija ottaa todella kuvakaappauksen ja sendFrameBufferUpdate () suoritetaan.

Yllä olevassa kuvassa näkyy kuuntelijan suhde kehyspuskurin päivitysprosessiin. Suurin osa kuuntelijoista laukeaa, kun käyttäjä suorittaa toimintoja: napsauttaa, valitsee tekstiä, kirjoittaa jotain tekstialueelle jne. Sitten jäsentoiminto doIncrementalFramebufferUpdate () suoritetaan, joka käynnistää ajastimen timerUpdateFrameBuffer . Ajastin lopulta soittaa sendFrameBufferUpdate () menetelmä RFBService-luokassa ja se aiheuttaa näytön päivityksen asiakaspuolella (VNC-katseluohjelma).
Sieppaa näyttö, toista näppäinyhdistelmiä ja siirrä hiiren osoitinta näytöllä
Javalla on sisäänrakennettu robottiluokka, jonka avulla kehittäjä voi kirjoittaa sovelluksen, joka tarttuu kuvakaappauksiin, lähettää avaimia, manipuloi hiiren osoitinta, tuottaa napsautuksia jne.
RobotScreen käytetään tarttumaan näytön alueeseen, jossa JFrame-ikkuna näkyy. Tärkein menetelmä on getScreenshot (int, int, int, int) joka sieppaa näytön alueen. RGB-arvot kullekin pikselille tallennetaan int [] -taulukkoon:
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; }
Method tallentaa pikselit colorImageBuffer-ryhmään. Saadaksesi pikselidatan, getColorImageBuffer () menetelmää voidaan käyttää.
Method tallentaa myös edellisen kuvapuskurin. On mahdollista saada vain muutettuja pikseleitä. Käytä menetelmää, jotta saat vain kuvan alueen eron getDeltaImageBuffer () .
Robot-luokassa on helppoa lähettää näppäinpainalluksia järjestelmään. Jotkin VNC-katsojilta saadut erityiset avainkoodit on kuitenkin ensin käännettävä oikein. Luokan RobotKeyboardilla on menetelmä sendKey (int, int) joka käsittelee erikoisnäppäimiä ja aakkosnumeerisia avaimia:
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); } }
Argumenttitila määrittää, painetaanko näppäintä vai vapautetaanko se. Kun avainkoodi on käännetty oikein VT-vakiona, menetelmä doType (int, int) välitä avaimen arvo robotille ja vaikutus on sama kuin paikallinen käyttäjä on napsauttanut näppäimistön näppäintä:
private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }
Samanlainen kuin RobotKeyboard on RobotMouse-luokka, joka käsittelee osoitintapahtumia ja saa hiiren osoittimen liikkumaan ja napsauttamaan.
public void mouseMove(int x, int y) { robot.mouseMove(x, y); }
Kaikki kolme luokkaa RobotScreen, RobotMouse ja RobotKeyboard jakavat uuden Robot-esiintymän konstruktorissa:
this.robot = new Robot();
Meillä on vain yksi esiintymä kustakin, koska sovellustasolla ei tarvitse olla useampaa kuin yhtä RobotScreen-, RobotMouse- tai RobotKeyboard-luokan esiintymää.
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(); ... }
Tässä esittelysovelluksessa nämä esiintymät luodaan main () toiminto.
Tuloksena on Swing-pohjainen Java-sovellus, joka toimii RFB-palveluntarjoajana ja jonka avulla tavalliset VNC-katsojat voivat muodostaa yhteyden siihen:

Johtopäätös
RFB-protokollaa käytetään laajalti ja hyväksytään. Asiakastoteutuksia VNC-katsojina on olemassa melkein kaikille alustoille ja laitteille. Päätarkoitus on näyttää pöytätietokoneet etänä, mutta voi olla myös muita sovelluksia. Voit esimerkiksi luoda hienoja graafisia työkaluja ja käyttää niitä etänä parantaaksesi olemassa olevaa etätyönkulut .
Tässä artikkelissa käsitellään RFB-protokollan perusteita, sanomamuotoa, osan näytön lähettämistä sekä näppäimistön ja hiiren käsittelyä. Täydellinen lähdekoodi Swing-demosovelluksella on saatavilla GitHubissa .