Στην πληροφορική, το Virtual Network Computing (VNC) είναι ένα γραφικό σύστημα κοινής χρήσης επιτραπέζιου υπολογιστή που χρησιμοποιεί το πρωτόκολλο Remote Framebuffer (RFB) για τον έλεγχο από απόσταση ενός άλλου υπολογιστή. Μεταδίδει συμβάντα πληκτρολογίου και ποντικιού από τον έναν υπολογιστή στον άλλο και αναμεταδίδει ενημερώσεις οθόνης γραφικών προς την άλλη κατεύθυνση μέσω δικτύου.
Το RFB είναι ένα απλό πρωτόκολλο για απομακρυσμένη πρόσβαση σε γραφικές διεπαφές χρήστη. Επειδή λειτουργεί σε επίπεδο buffer πλαισίου, ισχύει για όλα τα συστήματα και εφαρμογές Window, συμπεριλαμβανομένων των Microsoft Windows, Mac OS X και X Window System.
Σε αυτό το άρθρο θα δείξω πώς να εφαρμόσω το πρωτόκολλο διακομιστή RFB και θα δείξω με μια μικρή εφαρμογή Java Swing πώς να μεταδώσετε το κύριο παράθυρο μέσω σύνδεσης TCP σε θεατές VNC. Η ιδέα είναι να δείξουμε βασικά χαρακτηριστικά του πρωτοκόλλου και πιθανή εφαρμογή στην Java.
Ο αναγνώστης πρέπει να έχει βασικές γνώσεις για τη γλώσσα προγραμματισμού Java και θα πρέπει να είναι εξοικειωμένος με τις βασικές έννοιες της δικτύωσης TCP / IP, του μοντέλου διακομιστή-πελάτη κ.λπ. Ιδανικά, ο αναγνώστης είναι Προγραμματιστής Java και έχει κάποια εμπειρία με γνωστές εφαρμογές VNC όπως RealVNC, UltraVNC, TightVNC κ.λπ.
Η προδιαγραφή πρωτοκόλλου RFB είναι όμορφη καλά καθορισμένο . Σύμφωνα με την Wikipedia, το πρωτόκολλο RFB έχει διάφορες εκδόσεις. Για αυτό το άρθρο, θα επικεντρωθούμε σε κοινά μηνύματα που πρέπει να κατανοηθούν σωστά από τις περισσότερες εφαρμογές VNC ανεξάρτητα από την έκδοση πρωτοκόλλου.
διαφορά μεταξύ s corporation και llc
Αφού ένα πρόγραμμα προβολής VNC (πελάτης) δημιουργήσει σύνδεση TCP σε διακομιστή VNC (υπηρεσία RFB), η πρώτη φάση περιλαμβάνει την ανταλλαγή της έκδοσης πρωτοκόλλου:
RFB Service ----------- 'RFB 003.003
' -------> VNC viewer RFB Service <---------- 'RFB 003.008
' -------- VNC viewer
Είναι μια απλή ροή byte που μπορεί να αποκωδικοποιηθεί Χαρακτήρες ASCII , όπως 'RFB 003.008 n'.
Μόλις γίνει αυτό, το επόμενο βήμα είναι ο έλεγχος ταυτότητας. Ο διακομιστής VNC στέλνει έναν πίνακα byte για να υποδείξει τον τύπο ελέγχου ταυτότητας που υποστηρίζει. Για παράδειγμα:
RFB Service ----------- 0x01 0x02 -----------> VNC viewer RFB Service <----------- 0x02 ----------- VNC viewer
Εδώ ο διακομιστής VNC έστειλε μόνο 1 πιθανό τύπο ελέγχου ταυτότητας (0x02). Το πρώτο byte 0x01 δηλώνει τον αριθμό των διαθέσιμων τύπων ελέγχου ταυτότητας. Το πρόγραμμα προβολής VNC πρέπει να απαντήσει με την τιμή 0x02, καθώς αυτός είναι ο μόνος δυνατός τύπος που υποστηρίζεται από το διακομιστή σε αυτό το παράδειγμα.
Στη συνέχεια, ο διακομιστής θα στείλει πρόκληση ελέγχου ταυτότητας (ανάλογα με τον αλγόριθμο, υπάρχουν αρκετοί) και ο πελάτης πρέπει να απαντήσει με το κατάλληλο μήνυμα απόκρισης πρόκλησης και να περιμένει τον διακομιστή να επιβεβαιώσει την απόκριση. Μόλις γίνει έλεγχος ταυτότητας του πελάτη, μπορεί να συνεχίσει τη διαδικασία δημιουργίας συνεδρίας.
Ο απλούστερος τρόπος εδώ είναι να μην επιλέξετε καθόλου έλεγχο ταυτότητας. Το πρωτόκολλο RFB είναι πάντως ανασφαλές, ανεξάρτητα από τον μηχανισμό ελέγχου ταυτότητας. Εάν η ασφάλεια είναι σημαντική, ο σωστός τρόπος θα ήταν να διοχετεύσετε συνεδρίες RFB μέσω συνδέσεων VPN ή SSH.
Σε αυτό το σημείο, το πρόγραμμα προβολής VNC στέλνει ένα κοινόχρηστο μήνυμα επιτραπέζιου υπολογιστή που λέει εάν ο πελάτης θα μοιραστεί και θα επιτρέψει σε άλλους θεατές VNC να συνδεθούν στον ίδιο υπολογιστή. Εναπόκειται στην εφαρμογή της υπηρεσίας RFB να εξετάσει αυτό το μήνυμα και ενδεχομένως να εμποδίσει πολλούς θεατές VNC να μοιραστούν την ίδια οθόνη. Αυτό το μήνυμα έχει μήκος μόνο 1 byte και μια έγκυρη τιμή είναι 0x00 ή 0x01.
Τέλος, ο διακομιστής RFB στέλνει ένα μήνυμα init διακομιστή, ο οποίος περιέχει διάσταση οθόνης, bits ανά pixel, βάθος, μεγάλη σημαία endian και πραγματικές χρωματικές σημαίες, μέγιστες τιμές για κόκκινα, πράσινα και μπλε χρώματα, θέσεις bit σε pixel για κόκκινα, πράσινα και μπλε χρώματα , και συμβολοσειρά / τίτλος επιφάνειας εργασίας. Τα πρώτα δύο byte αντιπροσωπεύουν το πλάτος της οθόνης σε pixel, ενώ τα επόμενα δύο byte είναι το ύψος της οθόνης. Μετά το byte ύψους οθόνης, θα πρέπει να υπάρχουν bits ανά pixel byte στο μήνυμα. Η τιμή είναι συνήθως 8, 16 ή 32. Στα περισσότερα σύγχρονα συστήματα με πλήρες εύρος χρωμάτων, τα bit ανά pixel byte έχουν τιμή 32 (0x20). Λέει στον πελάτη ότι μπορεί να ζητήσει πλήρες χρώμα για κάθε pixel από το διακομιστή. Το μεγάλο endian byte δεν είναι μηδενικό μόνο εάν τα pixel είναι σε μεγάλη σειρά endian. Εάν το πραγματικό byte χρώματος δεν είναι μηδέν (true), τότε τα επόμενα έξι byte καθορίζουν τον τρόπο εξαγωγής των εντάσεων χρώματος κόκκινου, πράσινου και μπλε από την τιμή των pixel. Τα επόμενα έξι byte είναι οι μέγιστες επιτρεπόμενες τιμές για κόκκινο, πράσινο και μπλε στοιχείο pixel. Αυτό είναι σημαντικό σε λειτουργία χρώματος 8-bit, όπου μόνο λίγα bits είναι διαθέσιμα για κάθε χρωματικό στοιχείο. Οι μετατοπίσεις κόκκινου, πράσινου και μπλε καθορίζουν τις θέσεις bit για κάθε χρώμα. Τα τελευταία τρία byte γεμίζουν και πρέπει να αγνοηθούν από τον πελάτη. Μετά τη μορφή pixel, υπάρχει ένα byte που καθορίζει το μήκος μιας συμβολοσειράς για τον τίτλο της επιφάνειας εργασίας. Ο τίτλος της επιφάνειας εργασίας είναι μια κωδικοποιημένη ASCII συμβολοσειρά σε πίνακα byte αυθαίρετου μήκους.
Μετά το μήνυμα init διακομιστή, η υπηρεσία RFB θα πρέπει να διαβάζει μηνύματα πελάτη από την υποδοχή και να τα αποκωδικοποιεί. Υπάρχουν 6 τύποι μηνυμάτων:
Η τεκμηρίωση πρωτοκόλλου είναι αρκετά ακριβής και εξηγεί κάθε μήνυμα. Για κάθε μήνυμα, εξηγείται κάθε byte. Για παράδειγμα, το μήνυμα init διακομιστή:
Αριθμός byte | Τύπος | Περιγραφή |
---|---|---|
2 | U16 | πλάτος framebuffer |
2 | U16 | framebuffer-ύψος |
16 | PIXEL_FORMAT | μορφή διακομιστή-pixel |
4 | U32 | μήκος ονόματος |
μήκος ονόματος | Πίνακας U8 | συμβολοσειρά ονόματος |
Εδώ, το PIXEL_FORMAT είναι:
Αριθμός byte | Τύπος | Περιγραφή |
---|---|---|
ένας | U8 | bit ανά εικονοστοιχείο |
ένας | U8 | βάθος |
ένας | U8 | μεγάλη-τελική σημαία |
ένας | U8 | true-color-flag |
2 | U16 | κόκκινο-μέγ |
2 | U16 | πράσινο-μέγ |
2 | U16 | μπλε-μέγ |
ένας | U8 | κόκκινη μετατόπιση |
ένας | U8 | πράσινη μετατόπιση |
ένας | U8 | μπλε-μετατόπιση |
3 | υλικό παραγεμίσματος |
Το U16 σημαίνει ακέραιο 16-bit χωρίς υπογραφή (δύο byte), το U32 είναι ακέραιο 32-bit χωρίς υπογραφή, ο πίνακας U8 είναι πίνακας byte κ.λπ.
Μια τυπική εφαρμογή διακομιστή Java αποτελείται από ένα νήμα που ακούει για συνδέσεις πελατών και πολλά νήματα που χειρίζονται συνδέσεις πελατών.
/* * 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(); }
Εδώ επιλέχθηκε η θύρα TCP 5902 (οθόνη: 2) και ο loop loop περιμένει να συνδεθεί ένας πελάτης. Μέθοδος ServerSocket.accept () αποκλείει και κάνει το νήμα να περιμένει μια νέα σύνδεση πελάτη. Μόλις συνδεθεί ο πελάτης, δημιουργείται ένα νέο νήμα RFBService που χειρίζεται τα μηνύματα πρωτοκόλλου RFB που λαμβάνονται από τον πελάτη.
Το Class RFBService εφαρμόζει διεπαφή με δυνατότητα εκτέλεσης. Είναι γεμάτο μεθόδους ανάγνωσης bytes από την υποδοχή. Μέθοδος τρέξιμο() είναι σημαντικό, το οποίο εκτελείται αμέσως όταν το νήμα ξεκινά στο τέλος του βρόχου:
@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(); }
Εδώ μέθοδο αποστολήProtocolVersion () στέλνει συμβολοσειρά RFB στον πελάτη (πρόγραμμα προβολής VNC) και μετά διαβάζει τη συμβολοσειρά έκδοσης πρωτοκόλλου από τον πελάτη. Ο πελάτης πρέπει να απαντήσει με κάτι σαν 'RFB 003.008 n'. Μέθοδος ΔιαβάστεProtocolVersion () είναι φυσικά αποκλεισμός, όπως οποιαδήποτε μέθοδος του οποίου το όνομα ξεκινά με τη λέξη ανάγνωσης.
private String readProtocolVersion() throws IOException { byte[] buffer = readU8Array(12); return new String(buffer); }
Η μέθοδος readProtocolVersion () είναι απλή: διαβάζει 12 byte από την υποδοχή και επιστρέφει μια τιμή συμβολοσειράς. Η συνάρτηση readU8Array (int) διαβάζει καθορισμένο αριθμό byte, στην περίπτωση αυτή 12 bytes. Εάν δεν υπάρχουν αρκετά bytes για ανάγνωση στην υποδοχή, περιμένει:
private byte[] readU8Array(int len) throws IOException { byte[] buffer = new byte[len]; int offset = 0, left = buffer.length; while (offset Παρόμοιο με readU8Array (int) , μέθοδοι readU16int () και readU32int () υπάρχουν που διαβάζουν byte από την υποδοχή και επιστρέφουν ακέραια τιμή.
απλό πρότυπο εγγράφου τεχνικής σχεδίασης
Μετά την αποστολή της έκδοσης πρωτοκόλλου και την ανάγνωση της απάντησης, η υπηρεσία RFB θα πρέπει να στείλει μήνυμα ασφαλείας:
/* * 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();
Σε αυτήν την εφαρμογή, επιλέγεται ο απλούστερος τρόπος: δεν απαιτείται κωδικός πρόσβασης από την πλευρά του πελάτη VNC.
private void sendSecurityType() throws IOException { out.write(SECURITY_TYPE); out.flush(); }
όπου SECURITY_TYPE είναι πίνακας byte:
private final byte[] SECURITY_TYPE = {0x00, 0x00, 0x00, 0x01};
Αυτή η σειρά bytes με πρωτόκολλο RFB έκδοση 3.3 σημαίνει ότι το πρόγραμμα προβολής VNC δεν χρειάζεται να στείλει κωδικό πρόσβασης.
Στη συνέχεια, τι πρέπει να λάβει η υπηρεσία RFB από τον πελάτη είναι η κοινόχρηστη σημαία επιφάνειας εργασίας. Είναι ένα byte στην πρίζα.
/* * 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();
Μόλις διαβαστεί η κοινόχρηστη σημαία της επιφάνειας εργασίας από την υποδοχή, την αγνοούμε κατά την εφαρμογή μας.
Η υπηρεσία RFB πρέπει να στείλει μήνυμα στο διακομιστή:
/* * 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);
Η κλάση JFrameMainWindow είναι JFrame, η οποία είναι εδώ για επίδειξη ως πηγή γραφικών. Το μήνυμα init διακομιστή έχει υποχρεωτικό πλάτος και ύψος οθόνης σε pixel και τίτλο επιφάνειας εργασίας. Σε αυτό το παράδειγμα είναι ο τίτλος του JFrame που αποκτήθηκε με τη μέθοδο getTitle ().
Μετά το μήνυμα init διακομιστή, το νήμα υπηρεσίας RFB βγαίνει διαβάζοντας από την υποδοχή έξι τύπους μηνυμάτων:
/* * 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 () αποκλείει και ενεργοποιεί κάποια ενέργεια.
Για παράδειγμα, readClientCutText () Η μέθοδος διαβάζει κείμενο που κωδικοποιείται στο μήνυμα όταν ο χρήστης κόβει κείμενο από την πλευρά του πελάτη και στη συνέχεια ο θεατής VNC στέλνει κείμενο μέσω πρωτοκόλλου RFB στον διακομιστή. Στη συνέχεια, το κείμενο τοποθετείται στην πλευρά του διακομιστή στο Πρόχειρο.
Μηνύματα πελατών
Και τα έξι μηνύματα πρέπει να υποστηρίζονται από την υπηρεσία RFB, τουλάχιστον σε επίπεδο byte: όταν ο πελάτης στέλνει μήνυμα, πρέπει να διαβαστεί ένα πλήρες μήκος byte. Αυτό συμβαίνει επειδή το πρωτόκολλο RFB είναι προσανατολισμένο σε byte και δεν υπάρχει όριο μεταξύ δύο μηνυμάτων.
Το πιο σημαντικό μήνυμα εισαγωγής είναι το αίτημα ενημέρωσης καρέ buffer. Ο πελάτης μπορεί να ζητήσει πλήρη ενημέρωση ή σταδιακή ενημέρωση της οθόνης.
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 του μηνύματος αιτήματος buffer καρέ είναι ο τύπος μηνύματος. Η τιμή είναι πάντα 0x03. Το επόμενο byte είναι η σταδιακή σημαία, η οποία λέει στον διακομιστή να στείλει πλήρες καρέ ή απλά μια διαφορά. Σε περίπτωση πλήρους αιτήματος ενημέρωσης, η υπηρεσία RFB θα τραβήξει στιγμιότυπο οθόνης του κύριου παραθύρου χρησιμοποιώντας την κλάση RobotScreen και θα την στείλει στον πελάτη.
Εάν πρόκειται για αυξητικό αίτημα, επισημάνετε incrementalFrameBufferUpdate θα οριστεί ως αληθής. Αυτή η σημαία θα χρησιμοποιηθεί από τα στοιχεία Swing για να ελέγξει εάν πρέπει να στείλουν μέρη της οθόνης που έχουν αλλάξει. Συνήθως τα JMenu, JMenuItem, JTextArea, κ.λπ. πρέπει να κάνουν σταδιακή ενημέρωση της οθόνης όταν ο χρήστης μετακινεί το δείκτη του ποντικιού, κάνει κλικ, στέλνει πλήκτρα κλπ.
Η μέθοδος sendFrameBufferUpdate (int, int, int, int, int []) ξεπλένει το buffer εικόνας στην υποδοχή.
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(); }
Η μέθοδος ελέγχει ότι η συντεταγμένη (x, y) δεν βγαίνει από την οθόνη μαζί με το πλάτος x ύψος της προσωρινής μνήμης εικόνας. Η τιμή του τύπου μηνύματος για την ενημέρωση του buffer πλαισίου είναι 0x00. Η τιμή γεμίσματος είναι συνήθως 0x00 και πρέπει να αγνοηθεί από το πρόγραμμα προβολής VNC. Ο αριθμός των ορθογωνίων είναι τιμή δύο byte και καθορίζει πόσα ορθογώνια ακολουθούν στο μήνυμα.
Κάθε ορθογώνιο έχει πάνω αριστερή συντεταγμένη, πλάτος και ύψος, τύπο κωδικοποίησης και δεδομένα pixel. Υπάρχουν μερικές αποτελεσματικές μορφές κωδικοποίησης που μπορούν να χρησιμοποιηθούν, όπως το zrle, το hextile και το σφιχτό. Ωστόσο, για να διατηρήσουμε τα πράγματα απλά και εύκολα κατανοητά, θα χρησιμοποιήσουμε ακατέργαστη κωδικοποίηση στην εφαρμογή μας.
Η ακατέργαστη κωδικοποίηση σημαίνει ότι το χρώμα των pixel μεταδίδεται ως συστατικό RGB. Εάν ο πελάτης έχει ορίσει κωδικοποίηση pixel ως 32-bit, τότε μεταδίδονται 4 byte για κάθε pixel. Εάν ο πελάτης χρησιμοποιεί λειτουργία χρώματος 8-bit, τότε κάθε pixel μεταδίδεται ως 1 byte. Ο κωδικός εμφανίζεται στο for-loop. Σημειώστε ότι για τη λειτουργία 8 bit χρησιμοποιείται έγχρωμος χάρτης για την εύρεση της καλύτερης αντιστοίχισης για κάθε εικονοστοιχείο από το στιγμιότυπο οθόνης / την προσωρινή μνήμη εικόνας. Για τη λειτουργία 32-bit pixel, το buffer εικόνας περιέχει μια σειρά από ακέραιους αριθμούς, κάθε τιμή έχει πολλαπλά στοιχεία RGB.
Εφαρμογή επίδειξης Swing
Η εφαρμογή επίδειξης Swing περιέχει ακροατή δράσης που ενεργοποιεί sendFrameBufferUpdate (int, int, int, int, int []) μέθοδος. Συνήθως τα στοιχεία εφαρμογής, όπως τα στοιχεία Swing, πρέπει να έχουν ακροατές και να στέλνουν αλλαγή οθόνης στον πελάτη. Όπως όταν ο χρήστης πληκτρολογεί κάτι στο JTextArea, θα πρέπει να μεταδοθεί στο πρόγραμμα προβολής 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 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; } } } }
Ο κώδικας αυτού του ακροατή δράσης είναι πολύ απλός: παίρνει ένα στιγμιότυπο οθόνης του κύριου παραθύρου JFrameMain χρησιμοποιώντας την κλάση RobotScreen και, στη συνέχεια, καθορίζεται εάν απαιτείται μερική ενημέρωση της οθόνης. Μεταβλητός diffUpdateOfScreen χρησιμοποιείται ως σημαία για μερική ενημέρωση. Και τέλος, πλήρης προσωρινή μνήμη εικόνας ή μόνο διαφορετικές σειρές μεταδίδονται στον πελάτη. Αυτός ο κώδικας εξετάζει επίσης περισσότερους πελάτες συνδεδεμένους, γι 'αυτό χρησιμοποιείται ο επαναληπτής και διατηρείται η λίστα πελατών RFBDemo.rfbClientList μέλος.
Το πρόγραμμα ακρόασης δράσης ενημέρωσης Framebuffer θα μπορούσε να χρησιμοποιηθεί στο Χρονοδιακόπτη, το οποίο μπορεί να ξεκινήσει με οποιαδήποτε αλλαγή JComponent:
/* * Define timer for frame buffer update with 400 ms delay and * no repeat. */ timerUpdateFrameBuffer = new Timer(400, new ActionListenerFrameBufferUpdate()); timerUpdateFrameBuffer.setRepeats(false);
Αυτός ο κωδικός είναι στον κατασκευαστή της κλάσης JFrameMainWindow. Το χρονόμετρο ξεκινά με τη μέθοδο doIncrementalFrameBufferUpdate ():
πώς να χρησιμοποιήσετε το discord api
public void doIncrementalFrameBufferUpdate() { if (RFBDemo.rfbClientList.size() == 0) { return; } if (!timerUpdateFrameBuffer.isRunning()) { timerUpdateFrameBuffer.start(); } }
Οι άλλοι ακροατές δράσης καλούν συνήθως τη μέθοδο doIncrementalFrameBufferUpdate ():
public class DocumentListenerChange implements DocumentListener { @Override public void changedUpdate(DocumentEvent e) { JFrameMainWindow jFrameMainWindow = JFrameMainWindow.jFrameMainWindow; jFrameMainWindow.doIncrementalFrameBufferUpdate(); } // ... }
Αυτός ο τρόπος πρέπει να είναι απλός και εύκολο να ακολουθηθεί. Απαιτείται μόνο μια αναφορά στην παρουσία JFrameMainWindow και με μία κλήση doIncrementalFrameBufferUpdate () μέθοδος. Η μέθοδος θα ελέγξει εάν υπάρχουν συνδεδεμένοι πελάτες και, εάν υπάρχουν, χρονόμετρο timerUpdateFrameBuffer θα ξεκινήσει. Μόλις ξεκινήσει ο χρονοδιακόπτης, ο ακροατής δράσης θα λάβει πραγματικά στιγμιότυπο οθόνης και sendFrameBufferUpdate () εκτελείται.

Η παραπάνω εικόνα δείχνει τη σχέση του ακροατή με τη διαδικασία ενημέρωσης καρέ buffer. Οι περισσότεροι ακροατές ενεργοποιούνται όταν ο χρήστης κάνει ενέργεια: κάνει κλικ, επιλέγει κείμενο, πληκτρολογεί κάτι στην περιοχή κειμένου κ.λπ. Στη συνέχεια, η λειτουργία μέλους doIncrementalFramebufferUpdate () εκτελείται που ξεκινά το χρονόμετρο timerUpdateFrameBuffer . Ο χρονοδιακόπτης θα καλέσει τελικά sendFrameBufferUpdate () μέθοδος στην κλάση RFBService και θα προκαλέσει ενημέρωση οθόνης από την πλευρά του πελάτη (VNC viewer).
Λήψη οθόνης, αναπαραγωγή πλήκτρων και μετακίνηση δείκτη ποντικιού στην οθόνη
Η Java διαθέτει μια ενσωματωμένη κλάση ρομπότ που επιτρέπει στον προγραμματιστή να γράψει μια εφαρμογή που θα τραβήξει στιγμιότυπα οθόνης, θα στείλει κλειδιά, θα χειριστεί το δείκτη του ποντικιού, θα παράγει κλικ κ.λπ.
Για να αρπάξετε την περιοχή της οθόνης όπου εμφανίζεται το παράθυρο JFrame, χρησιμοποιείται το RobotScreen. Η κύρια μέθοδος είναι getScreenshot (int, int, int, int) που καταγράφει μια περιοχή οθόνης. Οι τιμές RGB για κάθε pixel αποθηκεύονται σε έναν πίνακα 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; }
Η μέθοδος αποθηκεύει εικονοστοιχεία σε πίνακα colorImageBuffer. Για να λάβετε δεδομένα pixel, getColorImageBuffer () μέθοδος μπορεί να χρησιμοποιηθεί.
Η μέθοδος αποθηκεύει επίσης το προηγούμενο buffer εικόνας. Είναι δυνατή η λήψη μόνο εικονοστοιχείων που έχουν αλλάξει. Για να λάβετε μόνο διαφορά περιοχής εικόνας, χρησιμοποιήστε τη μέθοδο getDeltaImageBuffer () .
Η αποστολή πλήκτρων στο σύστημα είναι εύκολη με την κατηγορία Robot. Ωστόσο, ορισμένοι ειδικοί κωδικοί κλειδιών που λαμβάνονται από τους θεατές VNC πρέπει πρώτα να μεταφραστούν σωστά. Η κλάση RobotKeyboard έχει μέθοδο sendKey (int, int) που χειρίζεται ειδικά πλήκτρα και αλφαριθμητικά πλήκτρα:
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); } }
Η κατάσταση του επιχειρήματος καθορίζει εάν το πλήκτρο πατηθεί ή απελευθερωθεί. Μετά τη σωστή μετάφραση του κλειδιού κώδικα σε VT σταθερά, μέθοδος doType (int, int) τιμή κλειδιού μεταβίβασης στο ρομπότ και το αποτέλεσμα είναι ίδιο με τον τοπικό χρήστη που έχει πατήσει το πλήκτρο στο πληκτρολόγιο:
private void doType(int keyCode, int state) { if (state == 0) { robot.keyRelease(keyCode); } else { robot.keyPress(keyCode); } }
Παρόμοια με το RobotKeyboard είναι η κλάση RobotMouse που χειρίζεται συμβάντα δείκτη και προκαλεί την κίνηση και το κλικ του δείκτη του ποντικιού.
public void mouseMove(int x, int y) { robot.mouseMove(x, y); }
Και οι τρεις τάξεις RobotScreen, RobotMouse και RobotKeyboard διαθέτουν νέα παρουσία ρομπότ στον κατασκευαστή:
this.robot = new Robot();
Έχουμε μόνο μία παρουσία καθεμιάς, αφού δεν χρειάζεται να υπάρχουν περισσότερες από μία εμφανίσεις κλάσης RobotScreen, RobotMouse ή 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(); ... }
Σε αυτήν την εφαρμογή επίδειξης δημιουργούνται αυτές οι παρουσίες κύριος() λειτουργία.
Το αποτέλεσμα είναι μια εφαρμογή που βασίζεται σε Swing στην Java, η οποία λειτουργεί ως πάροχος υπηρεσιών RFB και επιτρέπει στους τυπικούς θεατές VNC να συνδεθούν σε αυτό:

συμπέρασμα
Το πρωτόκολλο RFB χρησιμοποιείται ευρέως και γίνεται αποδεκτό. Υλοποιήσεις πελατών σε μορφή προβολέων VNC υπάρχουν για όλες σχεδόν τις πλατφόρμες και συσκευές. Ο κύριος σκοπός είναι να εμφανίζονται απομακρυσμένοι επιτραπέζιοι υπολογιστές, αλλά μπορεί να υπάρχουν και άλλες εφαρμογές. Για παράδειγμα, θα μπορούσατε να δημιουργήσετε όμορφα εργαλεία γραφικών και να αποκτήσετε πρόσβαση σε αυτά από απόσταση για να βελτιώσετε το υπάρχον απομακρυσμένες ροές εργασίας .
Αυτό το άρθρο καλύπτει τα βασικά του πρωτοκόλλου RFB, τη μορφή μηνύματος, τον τρόπο αποστολής μέρους της οθόνης και τον τρόπο αντιμετώπισης του πληκτρολογίου και του ποντικιού. Ο πλήρης πηγαίος κώδικας με την εφαρμογή επίδειξης Swing είναι διαθέσιμο στο GitHub .