Javaでのリモートフレームバッファサーバーの実装

公開: 2022-03-11

コンピューティングでは、Virtual Network Computing(VNC)は、リモートフレームバッファー(RFB)プロトコルを使用して別のコンピューターをリモート制御するグラフィカルデスクトップ共有システムです。 キーボードとマウスのイベントをあるコンピューターから別のコンピューターに送信し、グラフィカルな画面の更新をネットワークを介して反対方向に中継します。

RFBは、グラフィカルユーザーインターフェイスにリモートアクセスするためのシンプルなプロトコルです。 フレームバッファレベルで動作するため、Microsoft Windows、Mac OS X、XWindowSystemを含むすべてのウィンドウシステムとアプリケーションに適用できます。

JavaでのリモートFramebufferサーバー側プロトコルを利用したSwingアプリケーションの構築

JavaでのリモートFramebufferサーバー側プロトコルを利用したSwingアプリケーションの構築
つぶやき

この記事では、RFBサーバー側プロトコルを実装する方法を示し、小さなJava Swingアプリケーションを使用して、TCP接続を介してメインウィンドウをVNCビューアに送信する方法を示します。 アイデアは、プロトコルの基本的な機能とJavaでの可能な実装を示すことです。

読者は、Javaプログラミング言語の基本的な知識を持ち、TCP / IPネットワーク、クライアントサーバーモデルなどの基本的な概念に精通している必要があります。理想的には、読者はJava開発者であり、RealVNCなどのよく知られたVNC実装の経験があります。 、UltraVNC、TightVNCなど。

リモートフレームバッファプロトコル仕様

RFBプロトコル仕様はかなり明確に定義されています。 ウィキペディアによると、RFBプロトコルにはいくつかのバージョンがあります。 この記事では、プロトコルのバージョンに関係なく、ほとんどのVNC実装で正しく理解される必要がある一般的なメッセージに焦点を当てます。

VNCビューア(クライアント)がVNCサーバー(RFBサービス)へのTCP接続を確立した後、最初のフェーズにはプロトコルバージョンの交換が含まれます。

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

これは、「RFB003.008\n」などのASCII文字にデコードできる単純なバイトストリームです。

それが完了したら、次のステップは認証です。 VNCサーバーは、サポートする認証のタイプを示すためにバイトの配列を送信します。 例えば:

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

ここで、VNCサーバーは1つの可能な認証タイプ(0x02)のみを送信しました。 最初のバイト0x01は、使用可能な認証タイプの数を示します。 この例ではサーバーでサポートされている唯一のタイプであるため、VNCビューアは値0x02で応答する必要があります。

次に、サーバーは認証チャレンジを送信し(アルゴリズムによって異なりますが、いくつかあります)、クライアントは適切なチャレンジ応答メッセージで応答し、サーバーが応答を確認するのを待つ必要があります。 クライアントが認証されると、セッション確立のプロセスを続行できます。

ここでの最も簡単な方法は、認証をまったく選択しないことです。 認証メカニズムに関係なく、RFBプロトコルはとにかく安全ではありません。 セキュリティが重要な場合、適切な方法は、VPNまたはSSH接続を介してRFBセッションをトンネリングすることです。

この時点で、VNCビューアは、クライアントが共有し、他のVNCビューアが同じデスクトップに接続できるようにするかどうかを通知する共有デスクトップメッセージを送信します。 そのメッセージを考慮し、複数のVNCビューアが同じ画面を共有できないようにするのは、RFBサービスの実装次第です。 このメッセージの長さはわずか1バイトで、有効な値は0x00または0x01です。

最後に、RFBサーバーはサーバー初期化メッセージを送信します。このメッセージには、画面の寸法、ピクセルあたりのビット数、深度、ビッグエンディアンフラグとトゥルーカラーフラグ、赤、緑、青の色の最大値、赤、緑、青の色のピクセル単位のビット位置が含まれます。 、およびデスクトップの文字列/タイトル。 最初の2バイトはピクセル単位の画面幅を表し、次の2バイトは画面の高さです。 画面の高さバイトの後、ピクセルバイトあたりのビット数がメッセージに表示されます。 値は通常8、16、または32です。フルカラー範囲の最新のシステムでは、ピクセルバイトあたりのビット数は32(0x20)です。 サーバーに各ピクセルのフルカラーを要求できることをクライアントに通知します。 ピクセルがビッグエンディアンの順序である場合にのみ、ビッグエンディアンバイトはゼロ以外になります。 トゥルーカラーバイトがゼロ以外(true)の場合、次の6バイトは、ピクセル値から赤、緑、および青の色の強度を抽出する方法を指定します。 次の6バイトは、ピクセルの赤、緑、青のコンポーネントの最大許容値です。 これは、各カラーコンポーネントで使用できるビットが数ビットしかない8ビットカラーモードでは重要です。 赤、緑、および青のシフトは、各色のビット位置を決定します。 最後の3バイトはパディングであり、クライアントは無視する必要があります。 ピクセル形式の後に、デスクトップタイトルの文字列の長さを定義するバイトがあります。 デスクトップタイトルは、任意の長さのバイト配列のASCIIエンコード文字列です。

リモートフレームバッファサーバー-クライアントプロトコル:バージョン交換、認証、サーバー初期化メッセージ

リモートフレームバッファサーバー-クライアントプロトコル:バージョン交換、認証、サーバー初期化メッセージ
つぶやき

サーバーの初期化メッセージの後、RFBサービスはソケットからクライアントメッセージを読み取り、それらをデコードする必要があります。 メッセージには次の6種類があります。

  • SetPixelFormat
  • SetEncodings
  • FramebufferUpdateRequest
  • KeyEvent
  • PointerEvent
  • ClientCutText

プロトコルのドキュメントは非常に正確で、各メッセージについて説明しています。 メッセージごとに、すべてのバイトが説明されます。 たとえば、サーバーの初期化メッセージ:

バイト数タイプ説明
2 U16 フレームバッファ幅
2 U16 フレームバッファ-高さ
16 PIXEL_FORMAT サーバーピクセル形式
4 U32 名前-長さ
名前-長さU8アレイ名前-文字列

ここで、PIXEL_FORMATは次のとおりです。

バイト数タイプ説明
1 U8 ピクセルあたりのビット数
1 U8 深さ
1 U8 ビッグエンディアンフラグ
1 U8 トゥルーカラーフラグ
2 U16 赤-最大
2 U16 グリーンマックス
2 U16 ブルーマックス
1 U8 赤方偏移
1 U8 グリーンシフト
1 U8 青方偏移
3 パディング

U16は符号なし16ビット整数(2バイト)、U32は符号なし32ビット整数、U8配列はバイトの配列などを意味します。

Javaでのプロトコル実装

一般的なJavaサーバーアプリケーションは、クライアント接続をリッスンする1つのスレッドと、クライアント接続を処理する複数のスレッドで構成されます。

 /* * 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が選択され(display:2)、whileループはクライアントが接続するのを待ちます。 メソッドServerSocket.accept()はブロックしており、スレッドに新しいクライアント接続を待機させます。 クライアントが接続すると、クライアントから受信したRFBプロトコルメッセージを処理する新しいスレッドRFBServiceが作成されます。

クラスRFBServiceは、Runnableインターフェイスを実装します。 ソケットからバイトを読み取るメソッドがたくさんあります。 スレッドがループの終わりに開始されるとすぐに実行されるメソッド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ビューア)に送信してから、クライアントからプロトコルバージョン文字列を読み取ります。 クライアントは「RFB003.008\n」のように返信する必要があります。 もちろん、メソッドreadProtocolVersion()は、名前がreadという単語で始まるメソッドと同様にブロックされます。

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

RFBプロトコルバージョン3.3によるこのバイト配列は、VNCビューアがパスワードを送信する必要がないことを意味します。

次に、RFBサービスがクライアントから取得する必要があるのは、共有デスクトップフラグです。 ソケットの1バイトです。

 /* * 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であり、グラフィックのソースとしてデモ目的でここにあります。 サーバーの初期化メッセージには、必須の画面の幅と高さ(ピクセル単位)、およびデスクトップのタイトルがあります。 この例では、getTitle()メソッドによって取得されたJFrameのタイトルです。

サーバー初期化メッセージの後、RFBサービススレッドはソケットから6種類のメッセージを読み取ることによってループします。

 /* * 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プロトコルを介してサーバーにテキストを送信します。 次に、テキストはクリップボードのサーバー側に配置されます。

クライアントメッセージ

6つのメッセージはすべて、少なくともバイトレベルでRFBサービスによってサポートされている必要があります。クライアントがメッセージを送信するときは、バイト長全体を読み取る必要があります。 これは、RFBプロトコルがバイト指向であり、2つのメッセージ間に境界がないためです。

最も重要なメッセージは、フレームバッファの更新要求です。 クライアントは、画面の完全更新または増分更新を要求できます。

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

メソッドは、(x、y)座標が画像バッファの幅x高さとともに画面から外れないことを確認します。 フレームバッファ更新のメッセージタイプ値は0x00です。 パディング値は通常0x00であり、VNCビューアでは無視する必要があります。 長方形の数は2バイトの値であり、メッセージで続く長方形の数を定義します。

各長方形には、左上の座標、幅、高さ、エンコードタイプ、ピクセルデータがあります。 zrle、hextile、tightなど、使用できる効率的なエンコード形式がいくつかあります。 ただし、物事を単純で理解しやすいものにするために、実装ではrawエンコーディングを使用します。

生のエンコーディングとは、ピクセルカラーがRGBコンポーネントとして送信されることを意味します。 クライアントがピクセルエンコーディングを32ビットに設定している場合、ピクセルごとに4バイトが送信されます。 クライアントが8ビットカラーモードを使用する場合、各ピクセルは1バイトとして送信されます。 コードはforループで表示されます。 8ビットモードの場合、スクリーンショット/画像バッファから各ピクセルに最適なものを見つけるためにカラーマップが使用されることに注意してください。 32ビットピクセルモードの場合、画像バッファには整数の配列が含まれ、各値には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<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; } } } }

このアクションリスナーのコードは非常に単純です。RobotScreenクラスを使用してメインウィンドウJFrameMainのスクリーンショットを撮り、画面の部分的な更新が必要かどうかを判断します。 変数diffUpdateOfScreenは、部分更新のフラグとして使用されます。 そして最後に、完全な画像バッファまたは異なる行のみがクライアントに送信されます。 このコードは、接続されているより多くのクライアントも考慮します。そのため、イテレーターが使用され、クライアントリストはRFBDemo.rfbClientList<RFBService>メンバーで維持されます。

フレームバッファ更新アクションリスナーは、任意の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()メソッドの1回の呼び出しのみが必要です。 メソッドは、接続されているクライアントがあるかどうかを確認し、接続されている場合は、タイマーtimerUpdateFrameBufferが開始されます。 タイマーが開始されると、アクションリスナーは実際にスクリーンショットを撮り、 sendFrameBufferUpdate()が実行されます。

上の図は、リスナーとフレームバッファの更新手順の関係を示しています。 ほとんどのリスナーは、ユーザーがアクション(クリック、テキストの選択、テキスト領域への入力など)を実行したときにトリガーされます。次に、メンバー関数doIncrementalFramebufferUpdate()が実行され、タイマーtimerUpdateFrameBufferが開始されます。 タイマーは最終的にRFBServiceクラスのsendFrameBufferUpdate()メソッドを呼び出し、クライアント側(VNCビューアー)で画面を更新します。

画面をキャプチャし、キーストロークを再生し、画面上でマウスポインタを移動します

JavaにはRobotクラスが組み込まれており、開発者はスクリーンショットの取得、キーの送信、マウスポインターの操作、クリックの生成などを行うアプリケーションを作成できます。

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()を使用します。

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)はキー値を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の3つのクラスはすべて、コンストラクターに新しいRobotインスタンスを割り当てます。

 this.robot = new Robot();

アプリケーションレベルでRobotScreen、RobotMouse、またはRobotKeyboardクラスの複数のインスタンスを持つ必要がないため、それぞれのインスタンスは1つだけです。

 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()関数で作成されます。

その結果、JavaのSwingベースのアプリケーションがRFBサービスプロバイダーとして機能し、標準のVNCビューアーがそれに接続できるようになります。

結論

RFBプロトコルは広く使用され、受け入れられています。 VNCビューアの形式のクライアント実装は、ほとんどすべてのプラットフォームとデバイスに存在します。 主な目的はデスクトップをリモートで表示することですが、他のアプリケーションも存在する可能性があります。 たとえば、気の利いたグラフィカルツールを作成し、それらにリモートでアクセスして、既存のリモートワークフローを強化できます。

この記事では、RFBプロトコルの基本、メッセージ形式、画面の一部を送信する方法、およびキーボードとマウスの処理方法について説明します。 Swingデモアプリケーションを含む完全なソースコードは、GitHubで入手できます。