Menerapkan Server Framebuffer Jarak Jauh di Java

Diterbitkan: 2022-03-11

Dalam komputasi, Virtual Network Computing (VNC) adalah sistem berbagi desktop grafis yang menggunakan protokol Remote Framebuffer (RFB) untuk mengontrol komputer lain dari jarak jauh. Ini mentransmisikan acara keyboard dan mouse dari satu komputer ke komputer lain, dan menyampaikan pembaruan layar grafis kembali ke arah lain melalui jaringan.

RFB adalah protokol sederhana untuk akses jarak jauh ke antarmuka pengguna grafis. Karena bekerja pada tingkat buffer bingkai, ini berlaku untuk semua sistem dan aplikasi windowing termasuk Microsoft Windows, Mac OS X, dan X Window System.

Membangun aplikasi Swing yang didukung protokol sisi server Framebuffer Jarak Jauh di Java

Membangun aplikasi Swing yang didukung protokol sisi server Framebuffer Jarak Jauh di Java
Menciak

Pada artikel ini saya akan menunjukkan bagaimana menerapkan protokol sisi server RFB dan mendemonstrasikan dengan aplikasi Java Swing kecil cara mengirimkan jendela utama melalui koneksi TCP ke pemirsa VNC. Idenya adalah untuk mendemonstrasikan fitur dasar dari protokol dan kemungkinan implementasi di Java.

Pembaca harus memiliki pengetahuan dasar tentang bahasa pemrograman Java dan harus terbiasa dengan konsep dasar jaringan TCP/IP, model client-server, dll. Idealnya, pembaca adalah pengembang Java dan memiliki beberapa pengalaman dengan implementasi VNC terkenal seperti RealVNC , UltraVNC, TightVNC, dll.

Spesifikasi Protokol Framebuffer Jarak Jauh

Spesifikasi protokol RFB didefinisikan dengan cukup baik. Menurut Wikipedia, protokol RFB memiliki beberapa versi. Untuk artikel ini, fokus kami adalah pada pesan umum yang harus dipahami dengan baik oleh sebagian besar implementasi VNC terlepas dari versi protokolnya.

Setelah penampil VNC (klien) membuat koneksi TCP ke server VNC (layanan RFB), fase pertama melibatkan pertukaran versi protokol:

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

Ini adalah aliran byte sederhana yang dapat didekodekan menjadi karakter ASCII, seperti “RFB 003.008\n”.

Setelah selesai, langkah selanjutnya adalah otentikasi. Server VNC mengirimkan array byte untuk menunjukkan jenis otentikasi yang didukungnya. Sebagai contoh:

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

Di sini server VNC hanya mengirim 1 kemungkinan jenis otentikasi (0x02). Byte pertama 0x01 menunjukkan jumlah jenis otentikasi yang tersedia. Penampil VNC harus membalas dengan nilai 0x02, karena itulah satu-satunya jenis yang mungkin didukung oleh server dalam contoh ini.

Selanjutnya, server akan mengirimkan tantangan otentikasi (tergantung pada algoritma yang mana, ada beberapa), dan klien harus merespons dengan pesan respons tantangan yang tepat dan menunggu server untuk mengonfirmasi respons. Setelah klien diautentikasi, mereka dapat melanjutkan proses pembentukan sesi.

Cara paling sederhana di sini adalah memilih tanpa otentikasi sama sekali. Protokol RFB tetap tidak aman, terlepas dari mekanisme otentikasi. Jika keamanan penting, cara yang tepat adalah dengan melakukan tunnel sesi RFB melalui koneksi VPN atau SSH.

Pada titik ini, penampil VNC mengirim pesan desktop bersama yang memberi tahu apakah klien akan berbagi dan mengizinkan penampil VNC lain untuk terhubung ke desktop yang sama. Terserah implementasi layanan RFB untuk mempertimbangkan pesan itu dan mungkin mencegah beberapa pemirsa VNC berbagi layar yang sama. Pesan ini hanya berukuran 1 byte, dan nilai yang valid adalah 0x00 atau 0x01.

Akhirnya server RFB mengirimkan pesan init server, yang berisi dimensi layar, bit per piksel, kedalaman, bendera big endian dan bendera warna asli, nilai maksimum untuk warna merah, hijau dan biru, posisi bit dalam piksel untuk warna merah, hijau dan biru , dan string/judul desktop. Dua byte pertama mewakili lebar layar dalam piksel, dua byte berikutnya adalah tinggi layar. Setelah byte tinggi layar, bit per byte piksel harus ada dalam pesan. Nilainya biasanya 8, 16, atau 32. Pada kebanyakan sistem modern dengan rentang warna penuh, bit per piksel byte memiliki nilai 32 (0x20). Ini memberitahu klien bahwa ia dapat meminta warna penuh untuk setiap piksel dari server. Byte big endian bukan nol hanya jika piksel berada dalam urutan big endian. Jika byte warna sebenarnya bukan nol (benar) maka enam byte berikutnya menentukan cara mengekstrak intensitas warna merah, hijau, dan biru dari nilai piksel. Enam byte berikutnya adalah nilai maksimum yang diizinkan untuk komponen piksel merah, hijau, dan biru. Ini penting dalam mode warna 8-bit, di mana hanya sedikit bit yang tersedia untuk setiap komponen warna. Pergeseran merah, hijau, dan biru menentukan posisi bit untuk setiap warna. Tiga byte terakhir adalah padding dan harus diabaikan oleh klien. Setelah format piksel, ada byte yang mendefinisikan panjang string untuk judul desktop. Judul desktop adalah string yang disandikan ASCII dalam array byte dengan panjang sewenang-wenang.

Protokol server-klien Framebuffer jarak jauh: pertukaran versi, otentikasi, dan pesan init server

Protokol server-klien Framebuffer jarak jauh: pertukaran versi, otentikasi, dan pesan init server
Menciak

Setelah pesan init server, layanan RFB harus membaca pesan klien dari soket dan mendekodekannya. Ada 6 jenis pesan:

  • Atur Format Pixel
  • SetEncodings
  • Permintaan Pembaruan Framebuffer
  • Acara Kunci
  • Acara Penunjuk
  • KlienCutText

Dokumentasi protokol cukup tepat dan menjelaskan setiap pesan. Untuk setiap pesan, setiap byte dijelaskan. Misalnya, pesan init server:

Jumlah byte Jenis Keterangan
2 U16 lebar framebuffer
2 U16 tinggi framebuffer
16 PIXEL_FORMAT format-piksel-server
4 U32 nama-panjang
nama-panjang susunan U8 nama-string

Di sini, PIXEL_FORMAT adalah:

Jumlah byte Jenis Keterangan
1 U8 bit per piksel
1 U8 kedalaman
1 U8 bendera besar-endian
1 U8 benar-warna-bendera
2 U16 merah-maks
2 U16 green-max
2 U16 biru-maks
1 U8 pergeseran merah
1 U8 pergeseran hijau
1 U8 pergeseran biru
3 lapisan

U16 berarti integer 16-bit yang tidak ditandatangani (dua byte), U32 adalah integer 32-bit yang tidak ditandatangani, array U8 adalah array byte, dll.

Implementasi Protokol di Java

Aplikasi server Java yang khas terdiri dari satu utas yang mendengarkan koneksi klien, dan beberapa utas yang menangani koneksi klien.

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

Di sini TCP port 5902 dipilih (tampilan: 2), dan loop sementara menunggu klien untuk terhubung. Metode ServerSocket.accept() memblokir dan membuat utas menunggu koneksi klien baru. Setelah klien terhubung, utas baru RFBService dibuat yang menangani pesan protokol RFB yang diterima dari klien.

Kelas RFBService mengimplementasikan antarmuka Runnable. Penuh dengan metode untuk membaca byte dari soket. Metode run() penting, yang dieksekusi segera ketika utas dimulai di akhir loop:

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

Di sini metode sendProtocolVersion() mengirimkan string RFB ke klien (penampil VNC) dan kemudian membaca string versi protokol dari klien. Klien harus membalas dengan sesuatu seperti “RFB 003.008\n”. Metode readProtocolVersion() tentu saja memblokir, seperti metode apa pun yang namanya dimulai dengan kata baca.

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

Metode readProtocolVersion() sederhana: membaca 12 byte dari soket, dan mengembalikan nilai string. Fungsi readU8Array(int) membaca jumlah byte yang ditentukan, dalam hal ini 12 byte. Jika tidak ada cukup byte untuk dibaca di soket, ia menunggu:

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

Mirip dengan readU8Array(int) , ada metode readU16int() dan readU32int() yang membaca byte dari soket dan mengembalikan nilai integer.

Setelah mengirim versi protokol dan membaca respons, layanan RFB harus mengirim pesan keamanan:

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

Dalam implementasi ini, cara paling sederhana dipilih: tidak memerlukan kata sandi apa pun dari sisi klien VNC.

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

di mana SECURITY_TYPE adalah array byte:

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

Array byte oleh protokol RFB versi 3.3 ini berarti bahwa penampil VNC tidak perlu mengirim kata sandi apa pun.

Selanjutnya apa yang harus didapatkan layanan RFB dari klien adalah bendera desktop bersama. Ini satu byte pada soket.

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

Setelah bendera desktop bersama dibaca dari soket, kami mengabaikannya dalam implementasi kami.

Layanan RFB harus mengirim pesan init server:

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

Kelas JFrameMainWindow adalah JFrame, yang ada di sini untuk tujuan demo sebagai sumber grafik. Pesan init server memiliki lebar dan tinggi layar wajib dalam piksel, dan judul desktop. Dalam contoh ini, judul JFrame diperoleh dengan metode getTitle().

Setelah pesan init server, utas layanan RFB berputar dengan membaca dari soket enam jenis pesan:

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

Setiap metode readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , ... readClientCutText() memblokir dan memicu beberapa tindakan.

Misalnya, metode readClientCutText() membaca teks yang dikodekan dalam pesan ketika pengguna memotong teks di sisi klien dan kemudian VNC viewer mengirimkan teks melalui protokol RFB ke server. Teks kemudian ditempatkan di sisi server di Clipboard.

Pesan Klien

Keenam pesan harus didukung oleh layanan RFB, setidaknya pada tingkat byte: ketika klien mengirim pesan, panjang byte penuh harus dibaca. Ini karena protokol RFB berorientasi byte dan tidak ada batas antara dua pesan.

Pesan yang paling banyak diimpor adalah permintaan pembaruan buffer bingkai. Klien dapat meminta pembaruan penuh atau pembaruan layar tambahan.

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

Byte pertama dari pesan permintaan frame buffer adalah tipe pesan. Nilainya selalu 0x03. Byte berikutnya adalah bendera tambahan, yang memberitahu server untuk mengirim bingkai penuh atau hanya perbedaan. Dalam hal permintaan pembaruan penuh, layanan RFB akan mengambil tangkapan layar dari jendela utama menggunakan kelas RobotScreen dan mengirimkannya ke klien.

Jika ini adalah permintaan tambahan, flag incrementalFrameBufferUpdate akan disetel ke true. Bendera ini akan digunakan oleh komponen Swing untuk memeriksa apakah mereka perlu mengirim bagian layar yang telah diubah. Biasanya JMenu, JMenuItem, JTextArea, dll. perlu melakukan pembaruan layar secara bertahap ketika pengguna menggerakkan penunjuk mouse, mengklik, mengirim penekanan tombol, dll.

Metode sendFrameBufferUpdate(int, int, int, int, int[]) mem-flush buffer gambar ke soket.

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

Metode memeriksa bahwa koordinat (x, y) tidak keluar dari layar bersama dengan lebar x tinggi buffer gambar. Nilai jenis pesan untuk pembaruan buffer bingkai adalah 0x00. Nilai padding biasanya 0x00 dan harus diabaikan oleh penampil VNC. Jumlah persegi panjang adalah nilai dua byte dan menentukan berapa banyak persegi panjang yang mengikuti pesan.

Setiap persegi panjang memiliki koordinat kiri atas, lebar, dan tinggi, jenis pengkodean dan data piksel. Ada beberapa format encoding yang efisien yang dapat digunakan, seperti zrle, hextile, dan tight. Namun, untuk menjaga hal-hal sederhana dan mudah dipahami, kami akan menggunakan pengkodean mentah dalam implementasi kami.

Encoding mentah berarti bahwa warna piksel ditransmisikan sebagai komponen RGB. Jika klien telah menetapkan pengkodean piksel sebagai 32-bit, maka 4 byte ditransmisikan untuk setiap piksel. Jika klien menggunakan mode warna 8-bit, maka setiap piksel ditransmisikan sebagai 1 byte. Kode ditampilkan dalam for-loop. Perhatikan bahwa untuk mode 8-bit peta warna digunakan untuk menemukan kecocokan terbaik untuk setiap piksel dari tangkapan layar / buffer gambar. Untuk mode piksel 32-bit, buffer gambar berisi array bilangan bulat, setiap nilai memiliki komponen RGB yang dimultipleks.

Aplikasi Demo Swing

Aplikasi demo ayunan berisi pendengar tindakan yang memicu metode sendFrameBufferUpdate(int, int, int, int, int[]) . Biasanya elemen aplikasi, seperti komponen Swing, harus memiliki pendengar dan mengirim perubahan layar ke klien. Seperti ketika pengguna mengetik sesuatu di JTextArea, itu harus dikirimkan ke penampil 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; } } } }

Kode pendengar tindakan ini cukup sederhana: dibutuhkan tangkapan layar dari jendela utama JFrameMain menggunakan kelas RobotScreen, kemudian ditentukan apakah pembaruan sebagian layar diperlukan. Variabel diffUpdateOfScreen digunakan sebagai tanda untuk pembaruan sebagian. Dan akhirnya buffer gambar lengkap atau hanya baris yang berbeda yang ditransmisikan ke klien. Kode ini juga mempertimbangkan lebih banyak klien yang terhubung, itulah sebabnya iterator digunakan dan daftar klien dipertahankan di anggota RFBDemo.rfbClientList<RFBService> .

Pendengar tindakan pembaruan Framebuffer dapat digunakan di Timer yang dapat dimulai dengan perubahan JComponent apa pun:

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

Kode ini ada di konstruktor kelas JFrameMainWindow. Timer dimulai dalam metode doIncrementalFrameBufferUpdate():

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

Pemroses tindakan lain biasanya memanggil metode doIncrementalFrameBufferUpdate():

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

Cara ini harus sederhana dan mudah diikuti. Hanya referensi ke instance JFrameMainWindow yang diperlukan dan satu panggilan metode doIncrementalFrameBufferUpdate() . Metode akan memeriksa apakah ada klien yang terhubung, dan jika ada, timer timerUpdateFrameBuffer akan dimulai. Setelah penghitung waktu dimulai, pendengar tindakan akan benar-benar mengambil tangkapan layar dan sendFrameBufferUpdate() dijalankan.

Gambar di atas menunjukkan hubungan listener dengan prosedur update frame buffer. Sebagian besar pendengar dipicu ketika pengguna melakukan tindakan: mengklik, memilih teks, mengetik sesuatu di area teks, dll. Kemudian fungsi anggota doIncrementalFramebufferUpdate() dijalankan yang memulai timer timerUpdateFrameBuffer . Timer pada akhirnya akan memanggil metode sendFrameBufferUpdate() di kelas RFBService dan itu akan menyebabkan pembaruan layar di sisi klien (penampil VNC).

Tangkap Layar, Mainkan Keystrokes dan Gerakkan Mouse Pointer di Layar

Java memiliki kelas Robot bawaan yang memungkinkan pengembang untuk menulis aplikasi yang akan mengambil tangkapan layar, mengirim kunci, memanipulasi penunjuk tetikus, menghasilkan klik, dll.

Untuk mengambil area layar tempat jendela JFrame ditampilkan, RobotScreen digunakan. Metode utama adalah getScreenshot(int, int, int, int) yang menangkap wilayah layar. Nilai RGB untuk setiap piksel disimpan dalam array 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; }

Metode menyimpan piksel dalam array colorImageBuffer. Untuk mendapatkan data piksel, dapat digunakan metode getColorImageBuffer() .

Metode juga menyimpan buffer gambar sebelumnya. Dimungkinkan untuk mendapatkan hanya piksel yang telah diubah. Untuk mendapatkan hanya perbedaan area gambar, gunakan metode getDeltaImageBuffer() .

Mengirim penekanan tombol ke sistem menjadi mudah dengan kelas Robot. Namun, beberapa kode kunci khusus yang diterima dari pemirsa VNC harus diterjemahkan dengan benar terlebih dahulu. Kelas RobotKeyboard memiliki metode sendKey(int, int) yang menangani kunci khusus dan kunci alfanumerik:

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

Status argumen menentukan apakah tombol ditekan atau dilepaskan. Setelah terjemahan kode kunci yang benar menjadi konstanta VT, metode doType(int, int) meneruskan nilai kunci ke Robot dan efeknya sama seperti pengguna lokal telah menekan tombol pada keyboard:

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

Mirip dengan RobotKeyboard adalah kelas RobotMouse yang menangani peristiwa penunjuk, dan menyebabkan penunjuk tetikus bergerak dan mengklik.

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

Ketiga kelas RobotScreen, RobotMouse, dan RobotKeyboard mengalokasikan instance Robot baru di konstruktor:

 this.robot = new Robot();

Kami hanya memiliki satu instance masing-masing, karena tidak perlu pada level aplikasi untuk memiliki lebih dari satu instance kelas RobotScreen, RobotMouse, atau 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(); ... }

Dalam aplikasi demo ini, instance ini dibuat dalam fungsi main() .

Hasilnya, adalah aplikasi berbasis Swing di Jawa yang bertindak sebagai penyedia layanan RFB dan memungkinkan pemirsa VNC standar untuk terhubung dengannya:

Kesimpulan

Protokol RFB banyak digunakan dan diterima. Implementasi klien dalam bentuk VNC viewer ada untuk hampir semua platform dan perangkat. Tujuan utamanya adalah untuk menampilkan desktop dari jarak jauh, tetapi bisa juga ada aplikasi lain. Misalnya, Anda dapat membuat alat grafis yang bagus dan mengaksesnya dari jarak jauh untuk meningkatkan alur kerja jarak jauh yang ada.

Artikel ini mencakup dasar-dasar protokol RFB, format pesan, cara mengirim bagian layar, dan cara menangani keyboard dan mouse. Kode sumber lengkap dengan aplikasi demo Swing tersedia di GitHub.