تنفيذ خادم مخزن الإطارات البعيد في Java

نشرت: 2022-03-11

في مجال الحوسبة ، تعد حوسبة الشبكة الافتراضية (VNC) نظام مشاركة سطح مكتب رسوميًا يستخدم بروتوكول Remote Framebuffer (RFB) للتحكم عن بُعد في كمبيوتر آخر. ينقل أحداث لوحة المفاتيح والماوس من كمبيوتر إلى آخر ، وينقل تحديثات الشاشة الرسومية مرة أخرى في الاتجاه الآخر عبر الشبكة.

RFB هو بروتوكول بسيط للوصول عن بعد إلى واجهات المستخدم الرسومية. نظرًا لأنه يعمل على مستوى المخزن المؤقت للإطار ، فإنه ينطبق على جميع أنظمة النوافذ والتطبيقات بما في ذلك Microsoft Windows و Mac OS X و X Window System.

إنشاء تطبيق Swing يعمل بالبروتوكول من جانب الخادم عن بُعد في Java

إنشاء تطبيق Swing يعمل بالبروتوكول من جانب الخادم عن بُعد في Java
سقسقة

سأوضح في هذه المقالة كيفية تنفيذ بروتوكول جانب الخادم RFB وأشرح باستخدام تطبيق Java Swing صغير كيفية إرسال النافذة الرئيسية عبر اتصال TCP إلى عارضين VNC. الفكرة هي إظهار الميزات الأساسية للبروتوكول والتنفيذ المحتمل في Java.

يجب أن يكون لدى القارئ معرفة أساسية بلغة برمجة Java ويجب أن يكون على دراية بالمفاهيم الأساسية لشبكات TCP / IP ونموذج خادم العميل وما إلى ذلك. من الناحية المثالية ، يكون القارئ مطور Java ولديه بعض الخبرة في تطبيقات VNC المعروفة مثل RealVNC ، UltraVNC ، TightVNC ، إلخ.

مواصفات بروتوكول Framebuffer البعيد

مواصفات بروتوكول RFB محددة جيدًا. وفقًا لـ Wikipedia ، يحتوي بروتوكول RFB على عدة إصدارات. في هذه المقالة ، سينصب تركيزنا على الرسائل الشائعة التي يجب فهمها بشكل صحيح من قبل معظم تطبيقات VNC بغض النظر عن إصدار البروتوكول.

بعد أن ينشئ عارض VNC (العميل) اتصال TCP بخادم VNC (خدمة RFB) ، تتضمن المرحلة الأولى تبادل إصدار البروتوكول:

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

إنه تدفق بسيط من البايتات التي يمكن فك ترميزها إلى أحرف ASCII ، مثل “RFB 003.008 \ n”.

بمجرد القيام بذلك ، فإن الخطوة التالية هي المصادقة. يرسل خادم VNC مصفوفة من البايت للإشارة إلى نوع المصادقات التي يدعمها. علي سبيل المثال:

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

هنا أرسل خادم VNC نوع مصادقة واحد ممكن (0x02). يشير البايت الأول 0x01 إلى عدد أنواع المصادقة المتاحة. يجب على عارض VNC الرد بالقيمة 0x02 ، نظرًا لأن هذا هو النوع الوحيد الممكن الذي يدعمه الخادم في هذا المثال.

بعد ذلك ، سيرسل الخادم تحدي المصادقة (اعتمادًا على الخوارزمية ، هناك عدة خوارزمية) ، ويجب على العميل الاستجابة برسالة استجابة التحدي المناسبة والانتظار حتى يؤكد الخادم الاستجابة. بمجرد مصادقة العميل ، يمكنه متابعة عملية إنشاء الجلسة.

إن أبسط طريقة هنا هي اختيار عدم وجود مصادقة على الإطلاق. بروتوكول RFB غير آمن على أي حال ، بغض النظر عن آلية المصادقة. إذا كان الأمان مهمًا ، فإن الطريقة الصحيحة ستكون عبر نفق جلسات RFB عبر اتصالات VPN أو SSH.

في هذه المرحلة ، يرسل عارض VNC رسالة سطح مكتب مشتركة توضح ما إذا كان العميل سيشارك ويسمح لمشاهدي VNC الآخرين بالاتصال بنفس سطح المكتب. الأمر متروك لتنفيذ خدمة RFB للنظر في هذه الرسالة وربما منع العديد من مشاهدي VNC من مشاركة نفس الشاشة. يبلغ طول هذه الرسالة بايت واحد فقط ، والقيمة الصالحة هي إما 0x00 أو 0x01.

أخيرًا ، يرسل خادم RFB رسالة تهيئة الخادم ، والتي تحتوي على أبعاد الشاشة ، وبت لكل بكسل ، والعمق ، وعلم Endian كبير وأعلام ألوان حقيقية ، وقيم قصوى للألوان الأحمر والأخضر والأزرق ، ومواضع البت بالبكسل للألوان الأحمر والأخضر والأزرق ، وسلسلة / عنوان سطح المكتب. أول وحدتي بايت تمثل عرض الشاشة بالبكسل ، والبايتان التاليان يمثلان ارتفاع الشاشة. بعد بايتات ارتفاع الشاشة ، يجب أن تكون وحدات البايت لكل بكسل موجودة في الرسالة. عادةً ما تكون القيمة 8 أو 16 أو 32. في معظم الأنظمة الحديثة ذات النطاق اللوني الكامل ، تكون قيمة وحدات البت لكل بكسل هي 32 (0x20). يخبر العميل أنه يمكنه طلب ألوان كاملة لكل بكسل من الخادم. البايت الداخلي الكبير لا يكون صفريًا إلا إذا كانت وحدات البكسل بترتيب endian كبير. إذا كانت بايت اللون الحقيقي غير صفري (صحيح) ، فإن البايتات الستة التالية تحدد كيفية استخراج شدة اللون الأحمر والأخضر والأزرق من قيمة البكسل. البايتات الستة التالية هي القيم القصوى المسموح بها للمكون الأحمر والأخضر والأزرق للبكسل. هذا مهم في صيغة الألوان 8 بت ، حيث لا يتوفر سوى عدد قليل من البتات لكل مكون من مكونات اللون. تحدد الإزاحات الأحمر والأخضر والأزرق مواضع البت لكل لون. البايت الثلاثة الأخيرة عبارة عن حشوة ويجب أن يتجاهلها العميل. بعد تنسيق البكسل ، يوجد بايت يحدد طول سلسلة لعنوان سطح المكتب. عنوان سطح المكتب هو سلسلة ASCII مشفرة في مصفوفة بايت ذات طول عشوائي.

بروتوكول عميل الخادم Framebuffer البعيد: تبادل الإصدار والمصادقة ورسالة بدء الخادم

بروتوكول عميل الخادم Framebuffer البعيد: تبادل الإصدار والمصادقة ورسالة بدء الخادم
سقسقة

بعد رسالة بدء الخادم ، يجب على خدمة RFB قراءة رسائل العميل من المقبس وفك تشفيرها. هناك 6 أنواع من الرسائل:

  • تنسيق SetPixelFormat
  • ترميز
  • FramebufferUpdateRequest
  • الحدث الرئيسي
  • مؤشر الحدث
  • ClientCutText

وثائق البروتوكول دقيقة جدًا وتشرح كل رسالة. لكل رسالة ، يتم شرح كل بايت. على سبيل المثال ، رسالة تهيئة الخادم:

عدد البايت اكتب وصف
2 تحت 16 عرض الإطارات
2 تحت 16 ارتفاع الإطار الاحتياطي
16 PIXEL_FORMAT خادم تنسيق بكسل
4 U32 اسم الطول
اسم الطول مجموعة U8 اسم السلسلة

هنا ، PIXEL_FORMAT هي:

عدد البايت اكتب وصف
1 U8 بت لكل بكسل
1 U8 عمق
1 U8 علم كبير endian
1 U8 لون العلم الحقيقي
2 تحت 16 أحمر ماكس
2 تحت 16 جرين ماكس
2 تحت 16 بلو ماكس
1 U8 التحول الأحمر
1 U8 التحول الأخضر
1 U8 تحول الأزرق
3 حشوة

U16 تعني عددًا صحيحًا 16 بت بدون إشارة (2 بايت) ، U32 عبارة عن عدد صحيح 32 بت بدون إشارة ، مصفوفة U8 عبارة عن مصفوفة من البايت ، إلخ.

تنفيذ البروتوكول في جافا

يتكون تطبيق خادم 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) ، وتنتظر حلقة while اتصال العميل. الأسلوب يتم حظر ServerSocket.accept () ويجعل مؤشر الترابط ينتظر اتصال عميل جديد. بمجرد اتصال العميل ، يتم إنشاء سلسلة جديدة RFBService التي تتعامل مع رسائل بروتوكول RFB الواردة من العميل.

تطبق فئة RFBService واجهة قابلة للتشغيل. إنه مليء بالطرق لقراءة البايت من مأخذ التوصيل. الطريقة run () مهمة ، والتي يتم تنفيذها على الفور عندما يبدأ مؤشر الترابط في نهاية الحلقة:

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

هنا ترسل الطريقة sendProtocolVersion () سلسلة RFB إلى العميل (عارض VNC) ثم تقرأ سلسلة إصدار البروتوكول من العميل. يجب على العميل الرد بشيء مثل "RFB 003.008 \ n". الطريقة readProtocolVersion () هي بالطبع حظر ، مثل أي طريقة يبدأ اسمها بكلمة قراءة.

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

أسلوب readProtocolVersion () بسيط: يقرأ 12 بايت من مأخذ التوصيل ، ويعيد قيمة سلسلة. تقرأ الوظيفة readU8Array (int) عددًا محددًا من البايت ، في هذه الحالة 12 بايت. إذا لم تكن هناك وحدات بايت كافية للقراءة على المقبس ، فإنه ينتظر:

 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) ، توجد الطرق readU16int () و readU32int () التي تقرأ البايت من مأخذ التوصيل وترجع قيمة عدد صحيح.

بعد إرسال إصدار البروتوكول وقراءة الاستجابة ، يجب أن ترسل خدمة 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 عبارة عن مصفوفة بايت:

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

هذه المجموعة من البايت بواسطة الإصدار 3.3 من بروتوكول RFB تعني أن VNC viewer لا يحتاج إلى إرسال أي كلمة مرور.

بعد ذلك ، ما يجب أن تحصل عليه خدمة RFB من العميل هو علامة سطح المكتب المشتركة. إنه بايت واحد على المقبس.

 /* * 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 ، وهي هنا لغرض العرض كمصدر للرسومات. تحتوي رسالة بدء الخادم على عرض وارتفاع شاشة إلزاميين بالبكسل ، وعنوان سطح المكتب. في هذا المثال ، تم الحصول على عنوان JFrame بواسطة طريقة getTitle ().

بعد رسالة بدء الخادم ، حلقات مؤشر ترابط خدمة 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 جميع الرسائل الست ، على الأقل على مستوى البايت: عندما يرسل العميل رسالة ، يجب قراءة طول بايت كامل. وذلك لأن بروتوكول RFB موجه للبايت ولا يوجد حد بين رسالتين.

أكثر رسالة استيراد هي طلب تحديث المخزن المؤقت للإطار. قد يطلب العميل تحديثًا كاملاً أو تحديثًا تزايديًا للشاشة.

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

البايت الأول من رسالة طلب المخزن المؤقت للإطار هو نوع الرسالة. القيمة دائمًا هي 0x03. البايت التالي هو علامة تزايدية ، تخبر الخادم بإرسال إطار كامل أو مجرد اختلاف. في حالة طلب التحديث الكامل ، ستأخذ خدمة RFB لقطة شاشة للنافذة الرئيسية باستخدام فئة RobotScreen وإرسالها إلى العميل.

إذا كان طلبًا تزايديًا ، فسيتم تعيين علامة incrementalFrameBufferUpdate على true. سيتم استخدام هذه العلامة بواسطة مكونات Swing للتحقق مما إذا كانت بحاجة إلى إرسال أجزاء من الشاشة التي تم تغييرها. عادةً ما يحتاج JMenu و JMenuItem و JTextArea وما إلى ذلك إلى إجراء تحديث تدريجي للشاشة عندما يقوم المستخدم بتحريك مؤشر الماوس والنقر وإرسال ضغطات المفاتيح وما إلى ذلك.

أسلوب sendFrameBufferUpdate (int ، int ، int ، int ، int []) يمسح المخزن المؤقت للصورة إلى المقبس.

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

يتحقق الأسلوب من أن إحداثيات (س ، ص) لا تنفجر عن الشاشة مع عرض × ارتفاع المخزن المؤقت للصورة. قيمة نوع الرسالة لتحديث المخزن المؤقت للإطار هي 0x00. عادةً ما تكون قيمة المساحة المتروكة 0x00 ويجب تجاهلها بواسطة عارض VNC. عدد المستطيلات هو 2 بايت ويحدد عدد المستطيلات التالية في الرسالة.

يحتوي كل مستطيل على الإحداثيات والعرض والارتفاع الأيسر العلوي ونوع الترميز وبيانات البكسل. هناك بعض تنسيقات التشفير الفعالة التي يمكن استخدامها ، مثل zrle و hextile و tight. ومع ذلك ، لإبقاء الأمور بسيطة وسهلة الفهم ، سوف نستخدم التشفير الخام في تنفيذنا.

يعني التشفير الخام أن لون البكسل ينتقل كمكون RGB. إذا قام العميل بتعيين تشفير البكسل على أنه 32 بت ، فسيتم إرسال 4 بايت لكل بكسل. إذا كان العميل يستخدم وضع ألوان 8 بت ، فسيتم إرسال كل بكسل على هيئة 1 بايت. يظهر الرمز في حلقة for-loop. لاحظ أنه بالنسبة لوضع 8 بت ، يتم استخدام خريطة الألوان للعثور على أفضل تطابق لكل بكسل من مخزن لقطة الشاشة / الصورة. بالنسبة لوضع البكسل 32 بت ، يحتوي المخزن المؤقت للصورة على مصفوفة من الأعداد الصحيحة ، ولكل قيمة مكونات RGB مضاعفة.

تطبيق Swing Demo

يحتوي تطبيق 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<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; } } } }

رمز مستمع الإجراء هذا بسيط للغاية: يأخذ لقطة شاشة للنافذة الرئيسية JFrameMain باستخدام فئة RobotScreen ، ثم يتم تحديد ما إذا كانت هناك حاجة إلى تحديث جزئي للشاشة. يتم استخدام Variable diffUpdateOfScreen كعلامة للتحديث الجزئي. وأخيرًا يتم إرسال المخزن المؤقت الكامل للصور أو الصفوف المختلفة فقط إلى العميل. يعتبر هذا الرمز أيضًا المزيد من العملاء المتصلين ، ولهذا السبب يتم استخدام المكرر ويتم الاحتفاظ بقائمة العملاء في عضو RFBDemo.rfbClientList <RFBService> .

يمكن استخدام مستمع إجراء تحديث Framebuffer في Timer والذي يمكن أن يبدأ بأي تغيير 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 ():

 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 () .

يوضح الشكل أعلاه علاقة المستمع بإجراء تحديث المخزن المؤقت للإطار. يتم تشغيل معظم المستمعين عندما يقوم المستخدم بإجراء: النقرات ، واختيار النص ، وكتابة شيء ما في منطقة النص ، وما إلى ذلك. ثم يتم تنفيذ وظيفة العضو doIncrementalFramebufferUpdate () التي تبدأ المؤقت timerUpdateFrameBuffer . سوف يستدعي المؤقت في النهاية طريقة sendFrameBufferUpdate () في فئة RFBService وسيتسبب في تحديث الشاشة من جانب العميل (عارض VNC).

التقاط الشاشة وتشغيل ضغطات المفاتيح وتحريك مؤشر الماوس على الشاشة

تحتوي Java على فئة روبوت مدمجة تمكن المطور من كتابة تطبيق من شأنه التقاط لقطات شاشة وإرسال مفاتيح ومعالجة مؤشر الماوس وإنتاج نقرات وما إلى ذلك.

للاستيلاء على منطقة من الشاشة حيث يتم عرض نافذة JFrame ، يتم استخدام RobotScreen. الطريقة الرئيسية هي getScreenshot (int ، int ، int ، int) التي تلتقط منطقة من الشاشة. يتم تخزين قيم RGB لكل بكسل في مصفوفة 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. للحصول على بيانات البكسل ، يمكن استخدام طريقة getColorImageBuffer () .

يحفظ الأسلوب أيضًا المخزن المؤقت للصورة السابقة. من الممكن الحصول على وحدات البكسل التي تم تغييرها فقط. للحصول على اختلاف في مساحة الصورة فقط ، استخدم الطريقة getDeltaImageBuffer () .

يعد إرسال ضغطات المفاتيح إلى النظام أمرًا سهلاً مع فئة الروبوت. ومع ذلك ، يجب أولاً ترجمة بعض رموز المفاتيح الخاصة المستلمة من مشاهدي VNC بشكل صحيح. تحتوي Class 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) بتمرير قيمة المفتاح إلى Robot ويكون التأثير هو نفسه كما قام المستخدم المحلي بضرب المفتاح على لوحة المفاتيح:

 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 مثيل Robot جديد في المُنشئ:

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

في هذا التطبيق التجريبي ، يتم إنشاء هذه الطبعات في دالة main () .

والنتيجة هي تطبيق قائم على Swing في Java يعمل كمزود خدمة RFB ويسمح لمشاهدي VNC القياسيين بالاتصال به:

خاتمة

يستخدم بروتوكول RFB على نطاق واسع والمقبول. توجد تطبيقات العميل في شكل عارضين VNC لجميع الأنظمة الأساسية والأجهزة تقريبًا. الغرض الرئيسي هو عرض أجهزة سطح المكتب عن بُعد ، ولكن يمكن أن تكون هناك تطبيقات أخرى أيضًا. على سبيل المثال ، يمكنك إنشاء أدوات رسومية أنيقة والوصول إليها عن بُعد لتحسين مهام سير العمل عن بُعد الحالية.

تتناول هذه المقالة أساسيات بروتوكول RFB وتنسيق الرسائل وكيفية إرسال جزء من الشاشة وكيفية التعامل مع لوحة المفاتيح والماوس. كود المصدر الكامل مع تطبيق Swing التجريبي متاح على GitHub.