Java'da Uzak Çerçeve Tampon Sunucusu Uygulamak
Yayınlanan: 2022-03-11Bilgi işlemde, Virtual Network Computing (VNC), başka bir bilgisayarı uzaktan kontrol etmek için Remote Framebuffer (RFB) protokolünü kullanan bir grafik masaüstü paylaşım sistemidir. Klavye ve fare olaylarını bir bilgisayardan diğerine iletir ve grafik ekran güncellemelerini bir ağ üzerinden diğer yönde geri iletir.
RFB, grafiksel kullanıcı arayüzlerine uzaktan erişim için basit bir protokoldür. Çerçeve arabelleği düzeyinde çalıştığı için, Microsoft Windows, Mac OS X ve X Pencere Sistemi dahil olmak üzere tüm pencereleme sistemlerine ve uygulamalarına uygulanabilir.
Bu yazıda RFB sunucu tarafı protokolünün nasıl uygulanacağını ve küçük bir Java Swing uygulaması ile ana pencerenin TCP bağlantısı üzerinden VNC görüntüleyicilerine nasıl iletileceğini göstereceğim. Buradaki fikir, protokolün temel özelliklerini ve Java'daki olası uygulamayı göstermektir.
Okuyucu, Java programlama dili hakkında temel bilgiye sahip olmalı ve TCP/IP ağ iletişimi, istemci-sunucu modeli vb. ile ilgili temel kavramlara aşina olmalıdır. İdeal olarak, okuyucu bir Java geliştiricisidir ve RealVNC gibi iyi bilinen VNC uygulamaları konusunda biraz deneyime sahiptir. , UltraVNC, TightVNC, vb.
Uzak Çerçeve Tampon Protokolü Spesifikasyonu
RFB protokolü spesifikasyonu oldukça iyi tanımlanmıştır. Wikipedia'ya göre, RFB protokolünün birkaç versiyonu vardır. Bu makale için, protokol sürümünden bağımsız olarak çoğu VNC uygulaması tarafından doğru şekilde anlaşılması gereken yaygın mesajlara odaklanacağız.
Bir VNC görüntüleyici (istemci) bir VNC sunucusuna (RFB hizmeti) bir TCP bağlantısı kurduktan sonra, ilk aşama protokol sürümünün değişimini içerir:
RFB Service ----------- "RFB 003.003\n" -------> VNC viewer RFB Service <---------- "RFB 003.008\n" -------- VNC viewer
Bu, “RFB 003.008\n” gibi ASCII karakterlerine dönüştürülebilen basit bir bayt akışıdır.
Bu yapıldıktan sonra, bir sonraki adım kimlik doğrulamadır. VNC sunucusu, ne tür kimlik doğrulamalarını desteklediğini belirtmek için bir bayt dizisi gönderir. Örneğin:
RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer
Burada VNC sunucusu yalnızca 1 olası kimlik doğrulama türü (0x02) gönderdi. İlk bayt 0x01, mevcut kimlik doğrulama türlerinin sayısını belirtir. Bu örnekte sunucu tarafından desteklenen tek olası tür olduğundan, VNC görüntüleyici 0x02 değeriyle yanıt vermelidir.
Ardından, sunucu kimlik doğrulama sorgulaması gönderir (hangi algoritmaya bağlı olarak, birkaç tane vardır) ve istemcinin uygun sorgulama yanıt mesajıyla yanıt vermesi ve sunucunun yanıtı onaylamasını beklemesi gerekir. İstemcinin kimliği doğrulandıktan sonra, oturum oluşturma sürecine devam edebilirler.
Buradaki en basit yol, hiçbir kimlik doğrulaması seçmemektir. RFB protokolü, kimlik doğrulama mekanizmasından bağımsız olarak zaten güvensizdir. Güvenlik önemliyse, uygun yol RFB oturumlarını VPN veya SSH bağlantıları aracılığıyla tünellemek olacaktır.
Bu noktada, VNC görüntüleyici, istemcinin paylaşıp paylaşmayacağını ve diğer VNC görüntüleyicilerinin aynı masaüstüne bağlanmasına izin verip vermeyeceğini bildiren paylaşılan bir masaüstü mesajı gönderir. Bu mesajı dikkate almak ve muhtemelen birden fazla VNC görüntüleyicisinin aynı ekranı paylaşmasını engellemek RFB hizmetinin uygulanmasına bağlıdır. Bu ileti yalnızca 1 bayt uzunluğundadır ve geçerli bir değer 0x00 veya 0x01'dir.
Son olarak RFB sunucusu, ekran boyutu, piksel başına bit sayısı, derinlik, büyük endian bayrağı ve gerçek renk bayrakları, kırmızı, yeşil ve mavi renkler için maksimum değerler, kırmızı, yeşil ve mavi renkler için piksel cinsinden bit konumlarını içeren bir sunucu başlatma mesajı gönderir. , ve masaüstü dizesi/başlığı. İlk iki bayt piksel cinsinden ekran genişliğini, sonraki iki bayt ise ekran yüksekliğini gösterir. Ekran yüksekliği baytlarından sonra, mesajda piksel bayt başına bit bulunmalıdır. Değer genellikle 8, 16 veya 32'dir. Tam renk aralığına sahip modern sistemlerin çoğunda piksel bayt başına bit sayısı 32 (0x20) değerine sahiptir. İstemciye, sunucudan her piksel için tam renk isteyebileceğini söyler. Büyük endian baytı, yalnızca pikseller büyük endian düzendeyse sıfır değildir. Gerçek renk baytı sıfır değilse (doğru), sonraki altı bayt, piksel değerinden kırmızı, yeşil ve mavi renk yoğunluklarının nasıl çıkarılacağını belirtir. Sonraki altı bayt, pikselin kırmızı, yeşil ve mavi bileşeni için izin verilen maksimum değerlerdir. Bu, her bir renk bileşeni için yalnızca birkaç bitin mevcut olduğu 8 bit renk modunda önemlidir. Kırmızı, yeşil ve mavi kaymalar, her renk için bit konumlarını belirler. Son üç bayt dolgudur ve istemci tarafından göz ardı edilmelidir. Piksel biçiminden sonra, masaüstü başlığı için bir dizenin uzunluğunu tanımlayan bir bayt vardır. Masaüstü başlığı, keyfi uzunluktaki bayt dizisindeki ASCII kodlu bir dizedir.
Sunucu başlatma mesajından sonra, RFB hizmeti, istemci mesajlarını soketten okumalı ve kodunu çözmelidir. 6 tür mesaj vardır:
- Piksel Biçimini Ayarla
- Kodlamaları Ayarla
- FramebufferGüncellemeTalebi
- Önemli olay
- PointerEtkinliği
- ClientCutText
Protokol belgeleri oldukça kesindir ve her mesajı açıklar. Her mesaj için her bayt açıklanır. Örneğin, sunucu başlatma mesajı:
bayt sayısı | Tip | Tanım |
---|---|---|
2 | U16 | çerçeve arabelleği genişliği |
2 | U16 | çerçeve arabelleği yüksekliği |
16 | PIXEL_FORMAT | sunucu-piksel formatı |
4 | U32 | isim uzunluğu |
isim uzunluğu | U8 dizisi | isim dizisi |
Burada, PIXEL_FORMAT:
bayt sayısı | Tip | Tanım |
---|---|---|
1 | U8 | piksel başına bit |
1 | U8 | derinlik |
1 | U8 | büyük-endian-bayrak |
1 | U8 | gerçek renkli bayrak |
2 | U16 | kırmızı-maks |
2 | U16 | yeşil-maks |
2 | U16 | mavi-maks |
1 | U8 | kırmızıya kayma |
1 | U8 | yeşil vardiya |
1 | U8 | maviye kayma |
3 | dolgu malzemesi |
U16, işaretsiz 16 bit tam sayı (iki bayt) anlamına gelir, U32 işaretsiz 32 bit tam sayıdır, U8 dizisi bayt dizisidir, vb.
Java'da Protokol Uygulaması
Tipik bir Java sunucu uygulaması, istemci bağlantılarını dinleyen bir iş parçacığından ve istemci bağlantılarını işleyen birkaç iş parçacığından oluşur.
/* * 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(); }
Burada 5902 numaralı TCP bağlantı noktası seçilmiştir (ekran :2) ve while döngüsü bir istemcinin bağlanmasını bekler. Yöntem ServerSocket.accept() engelliyor ve iş parçacığının yeni bir istemci bağlantısı için beklemesini sağlıyor. İstemci bağlandığında, istemciden alınan RFB protokol mesajlarını işleyen yeni bir iş parçacığı RFBService oluşturulur.
Class RFBService, Runnable arabirimini uygular. Soketten bayt okuma yöntemleriyle dolu. Döngünün sonunda iş parçacığı başlatıldığında hemen yürütülen run() yöntemi önemlidir:
@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(); }
Burada sendProtocolVersion() yöntemi, istemciye (VNC görüntüleyici) RFB dizesi gönderir ve ardından istemciden protokol sürüm dizesini okur. Müşteri "RFB 003.008\n" gibi bir yanıt vermelidir. Yöntem readProtocolVersion() , adı read kelimesiyle başlayan herhangi bir yöntem gibi, elbette engellemedir.
private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }
readProtocolVersion() yöntemi basittir: soketten 12 bayt okur ve bir dize değeri döndürür. readU8Array(int) işlevi belirtilen sayıda baytı, bu durumda 12 baytı okur. Soket üzerinde okunacak yeterli bayt yoksa, bekler:
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; }
readU8Array(int) 'e benzer şekilde, soketten bayt okuyan ve tamsayı değeri döndüren readU16int() ve readU32int() yöntemleri mevcuttur.
Protokol versiyonunu gönderdikten ve yanıtı okuduktan sonra, RFB servisi güvenlik mesajı göndermelidir:
/* * 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();
Bu uygulamada, en basit yol seçilmiştir: VNC istemci tarafında herhangi bir parola gerektirmez.
private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }
SECURITY_TYPE bayt dizisidir:
private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};
RFB protokolü sürüm 3.3'e göre bu bayt dizisi, VNC görüntüleyicinin herhangi bir parola göndermesine gerek olmadığı anlamına gelir.
Daha sonra, RFB hizmetinin istemciden alması gereken paylaşılan masaüstü bayrağıdır. Soket üzerinde bir bayt.
/* * 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();
Soketten paylaşılan masaüstü bayrağı okunduğunda, uygulamamızda onu görmezden geliriz.
RFB hizmetinin sunucu başlatma mesajı göndermesi gerekir:
/* * 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);
Sınıf JFrameMainWindow, grafik kaynağı olarak demo amacıyla burada bulunan JFrame'dir. Sunucu başlatma mesajının piksel cinsinden zorunlu ekran genişliği ve yüksekliği ve masaüstü başlığı vardır. Bu örnekte, getTitle() yöntemiyle elde edilen JFrame'in başlığıdır.
Sunucu başlatma mesajından sonra, RFB hizmeti iş parçacığı, soketten altı tür mesaj okuyarak döngü yapar:
/* * 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); } }
readSetPixelFormat() , readSetEncoding() , readFrameBufferUpdateRequest() , … readClientCutText() yöntemlerinin her biri engelliyor ve bazı eylemleri tetikliyor.

Örneğin, readClientCutText() yöntemi, kullanıcı istemci tarafında metni kestiğinde ve ardından VNC görüntüleyici metni RFB protokolü aracılığıyla sunucuya gönderdiğinde mesajda kodlanan metni okur. Metin daha sonra Pano'da sunucu tarafına yerleştirilir.
Müşteri Mesajları
Altı mesajın tümü, en azından bayt düzeyinde RFB hizmeti tarafından desteklenmelidir: istemci mesaj gönderdiğinde, tam bir bayt uzunluğu okunmalıdır. Bunun nedeni, RFB protokolünün bayt yönelimli olması ve iki mesaj arasında sınır olmamasıdır.
En içe aktarılan mesaj, çerçeve arabelleği güncelleme isteğidir. Müşteri, ekranın tam veya kademeli olarak güncellenmesini talep edebilir.
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(); } }
Çerçeve arabelleği istek mesajının ilk baytı mesaj türüdür. Değer her zaman 0x03'tür. Sonraki bayt, sunucuya tam çerçeve veya yalnızca bir fark göndermesini söyleyen artımlı bayraktır. Tam güncelleme talebi olması durumunda, RFB servisi RobotScreen sınıfını kullanarak ana pencerenin ekran görüntüsünü alıp istemciye gönderecektir.
Artımlı istek ise, incrementalFrameBufferUpdate bayrağı true olarak ayarlanır. Bu bayrak, Swing bileşenleri tarafından ekranın değişen kısımlarını göndermeleri gerekip gerekmediğini kontrol etmek için kullanılacaktır. Genellikle JMenu, JMenuItem, JTextArea, vb., kullanıcı fare işaretçisini hareket ettirdiğinde, tıkladığında, tuş vuruşu gönderdiğinde vb. ekranın artımlı güncellemesini yapmalıdır.
SendFrameBufferUpdate(int, int, int, int, int[]) yöntemi, görüntü arabelleğini yuvaya boşaltır.
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(); }
Yöntem, (x, y) koordinatının, görüntü arabelleğinin genişlik x yüksekliği ile birlikte ekrandan çıkmadığını kontrol eder. Çerçeve arabelleği güncellemesi için mesaj tipi değeri 0x00'dır. Doldurma değeri genellikle 0x00'dır ve VNC görüntüleyici tarafından göz ardı edilmelidir. Dikdörtgen sayısı iki baytlık bir değerdir ve mesajda kaç tane dikdörtgenin takip edildiğini tanımlar.
Her dikdörtgenin sol üst koordinatı, genişliği ve yüksekliği, kodlama türü ve piksel verileri vardır. zrle, hextile ve sıkı gibi kullanılabilecek bazı verimli kodlama biçimleri vardır. Ancak, işleri basit ve anlaşılır kılmak için uygulamamızda ham kodlamayı kullanacağız.
Ham kodlama, piksel renginin RGB bileşeni olarak iletildiği anlamına gelir. İstemci piksel kodlamasını 32 bit olarak ayarladıysa, her piksel için 4 bayt iletilir. İstemci 8 bit renk modunu kullanıyorsa, her piksel 1 bayt olarak iletilir. Kod for döngüsünde gösterilir. 8 bitlik mod için, ekran görüntüsü / görüntü arabelleğinden her piksel için en iyi eşleşmeyi bulmak için renk haritasının kullanıldığını unutmayın. 32 bit piksel modu için, görüntü arabelleği tamsayı dizisi içerir, her değerin çoğullanmış RGB bileşenleri vardır.
Salıncak Demo Uygulaması
Swing demo uygulaması, sendFrameBufferUpdate(int, int, int, int, int[]) yöntemini tetikleyen eylem dinleyicisi içerir. Genellikle, Swing bileşenleri gibi uygulama öğelerinin dinleyicileri olması ve istemciye ekran değişikliği göndermesi gerekir. Örneğin, kullanıcı JTextArea'da bir şey yazdığında, VNC görüntüleyicisine iletilmelidir.
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; } } } }
Bu eylem dinleyicisinin kodu oldukça basittir: RobotScreen sınıfını kullanarak JFrameMain ana penceresinin ekran görüntüsünü alır, ardından ekranın kısmi güncellemesinin gerekli olup olmadığı belirlenir. Kısmi güncelleme için işaret olarak değişken diffUpdateOfScreen kullanılır. Ve son olarak, tam görüntü arabelleği veya yalnızca farklı satırlar istemciye iletilir. Bu kod aynı zamanda daha fazla istemcinin bağlı olduğunu da dikkate alır, bu nedenle yineleyici kullanılır ve istemci listesi RFBDemo.rfbClientList<RFBService> üyesinde tutulur.
Framebuffer güncelleme eylem dinleyicisi, herhangi bir JComponent değişikliği ile başlatılabilen Zamanlayıcı'da kullanılabilir:
/* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);
Bu kod, JFrameMainWindow sınıfının yapıcısındadır. Zamanlayıcı, doIncrementalFrameBufferUpdate() yönteminde başlatılır:
public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }
Diğer eylem dinleyicileri genellikle doIncrementalFrameBufferUpdate() yöntemini çağırır:
public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }
Bu yol basit ve takip edilmesi kolay olmalıdır. Yalnızca JFrameMainWindow örneğine bir başvuru ve tek bir doIncrementalFrameBufferUpdate() yöntemi çağrısı gereklidir. Yöntem, bağlı istemcilerin olup olmadığını kontrol edecek ve varsa timer timerUpdateFrameBuffer başlatılacaktır. Zamanlayıcı başlatıldığında, eylem dinleyicisi gerçekten ekran görüntüsünü alır ve sendFrameBufferUpdate() yürütülür.
Yukarıdaki şekil, çerçeve arabelleği güncelleme prosedürü ile dinleyici ilişkisini göstermektedir. Çoğu dinleyici, kullanıcı bir eylem gerçekleştirdiğinde tetiklenir: tıkladığında, metin seçtiğinde, metin alanına bir şey yazdığında vb. Daha sonra timerUpdateFrameBuffer'ı başlatan üye işlevi doIncrementalFramebufferUpdate() yürütülür. Zamanlayıcı sonunda RFBService sınıfında sendFrameBufferUpdate() yöntemini çağıracak ve istemci tarafında (VNC görüntüleyici) ekran güncellemesine neden olacaktır.
Ekranı Yakalayın, Tuş Vuruşlarını Oynatın ve Fare İşaretçisini Ekranda Hareket ettirin
Java, geliştiricinin ekran görüntüleri alacak, anahtar gönderecek, fare işaretçisini manipüle edecek, tıklama üretecek vb. bir uygulama yazmasını sağlayan yerleşik bir Robot sınıfına sahiptir.
JFrame penceresinin görüntülendiği ekran alanını almak için RobotScreen kullanılır. Ana yöntem, ekranın bir bölgesini yakalayan getScreenshot(int, int, int, int) yöntemidir. Her piksel için RGB değerleri bir int[] dizisinde saklanır:
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; }
Yöntem, pikselleri colorImageBuffer dizisinde saklar. Piksel verilerini almak için getColorImageBuffer() yöntemi kullanılabilir.
Yöntem ayrıca önceki görüntü arabelleğini de kaydeder. Yalnızca değiştirilen pikselleri elde etmek mümkündür. Yalnızca görüntü alanı farkını elde etmek için getDeltaImageBuffer() yöntemini kullanın.
Robot sınıfı ile tuş vuruşlarını sisteme göndermek kolaydır. Ancak, VNC izleyicilerinden alınan bazı özel anahtar kodların önce doğru şekilde çevrilmesi gerekir. Class RobotKeyboard, özel anahtarları ve alfasayısal anahtarları işleyen sendKey(int, int) yöntemine sahiptir:
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); } }
Argüman durumu, tuşa basılıp basılmadığını belirler. Anahtar kodunun VT sabitine doğru çevrilmesinden sonra, doType(int, int) yöntemi, anahtar değerini Robot'a iletir ve efekt, yerel kullanıcının klavyede tuşa basmasıyla aynıdır:
private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }
RobotKeyboard'a benzer şekilde, işaretçi olaylarını işleyen ve fare işaretçisinin hareket etmesine ve tıklamasına neden olan RobotMouse sınıfıdır.
public void mouseMove(int x, int y) { robot.mouseMove(x, y); }
RobotScreen, RobotMouse ve RobotKeyboard üç sınıfın tümü, yapıcıda yeni Robot örneğini tahsis eder:
this.robot = new Robot();
Uygulama düzeyinde birden fazla RobotScreen, RobotMouse veya RobotKeyboard sınıfı örneğine gerek olmadığından, her birinin yalnızca bir örneğine sahibiz.
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(); ... }
Bu demo uygulamasında bu örnekler main() işlevinde oluşturulur.
Sonuç, bir RFB servis sağlayıcısı olarak hareket eden ve standart VNC görüntüleyicilerinin ona bağlanmasına izin veren Java'da Swing tabanlı bir uygulamadır:
Çözüm
RFB protokolü yaygın olarak kullanılmakta ve kabul edilmektedir. Neredeyse tüm platformlar ve cihazlar için VNC görüntüleyicileri şeklindeki istemci uygulamaları mevcuttur. Ana amaç, masaüstlerini uzaktan görüntülemektir, ancak başka uygulamalar da olabilir. Örneğin, mevcut uzak iş akışlarınızı geliştirmek için şık grafik araçları oluşturabilir ve bunlara uzaktan erişebilirsiniz.
Bu makale, RFB protokolünün temellerini, mesaj formatını, ekranın bir bölümünün nasıl gönderileceğini ve klavye ve fare ile nasıl başa çıkılacağını kapsar. Swing demo uygulaması ile tam kaynak kodu GitHub'da mevcuttur.