2006 年 12 月 19 日 23 時 56 分

ProtocolVersion メッセージ


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


[写真]


設計だけがずっと続くとつまらないので、
軽いテストを含めながらやろう。

RFB の通信はメッセージの交換で行われる。
メッセージは、基本型で構成された可変長の構造体である。

RFB の通信で最初に行われるのは、ハンドシェイクである。
ハンドシェイクとは、クライアントとサーバの間で、
RFB のバージョンや認証、解像度などの交渉のことを表す。

ハンドシェイクの最初は、プロトコルバージョンの交渉だ。
RFB は何度か改定されており、いくつものバージョンがある。
そのため、最初に通信に使うバージョンを決めるのである。

この時に送受信されるメッセージは、
ProtocolVersion というメッセージである。
これは 12 バイト固定長の ASCII 文字列となっており、
プロトコルのバージョンを示す以下のような内容だ。

   HEX:   52 46 42 20 30 30 33 2e 30 30 38 0a
   ASCII: "RFB 003.008\n"

先頭の 4 バイトが "RFB " というマジックナンバー、
続く 3 バイトが 0 先導 10 進表記のメジャーバージョン、
続く 1 バイトはバージョンの区切り文字 '.'、
続く 3 バイトが 0 先導 10 進表記のマイナーバージョン、
そして最後に行送り文字が追加される。

ProtocolVersion は特殊なメッセージであり、
RFB の基本型でなく可読の文字列で表現している。
そのため、telnet 等で誤って接続しても検出できる。

バージョンの交渉は、最初にサーバがクライアント対して、
サーバの対応している最大のバージョン値を送信する。
RFB では、サーバがあるバージョンを返したとすると、
それ以下のバージョン全てに対応できることを意味している。

クライアントは、受信したサーバのバージョン以下で、
利用可能なバージョンを任意に選択してサーバに送り返す。
この往復によって、通信に使う RFB バージョンが決定する。

では、試してみよう。

まずは、RFB の主処理を行うクラスを作る。

========== RFBSession.java ==========

package jp.loafer.rfbdemo;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jp.loafer.rfb.io.RFBInputStream;
import jp.loafer.rfb.io.RFBOutputStream;

/**
* RFB プロトコルの処理。
* @author kes
*/
public class RFBSession {

    /**
     * {@link RFBSession} のインスタンスを作成。
     */
    public RFBSession() {
        // 処理不要
    }
   
    /**
     * RFB プロトコルの開始点。
     * @param in 汎用入力ストリーム。
     * @param out 汎用出力ストリーム。
     * @throws IOException 入出力エラー。
     */
    public void execute(InputStream in, OutputStream out)
            throws IOException {
        execute(new RFBInputStream(in), new RFBOutputStream(out));
    }

    /**
     * RFB プロトコルの開始点。
     * @param in RFB 入力ストリーム。
     * @param out RFB 出力ストリーム。
     * @throws IOException 入出力エラー。
     */
    public void execute(RFBInputStream in, RFBOutputStream out)
            throws IOException {
       
        // ProtocolVersion ハンドシェイク
       
        // サーバのバージョンを送信
        out.write("RFB 004.099\n".getBytes("us-ascii"));
        out.flush();

        // クライアントのバージョンを確認
        byte buffer[] = new byte[12];
        if (in.read(buffer) != 12) throw new IOException("RFB Protocol violation.");
       
        String message = new String(buffer, "us-ascii");
        Pattern p = Pattern.compile("^RFB (\\d{3})\\.(\\d{3})\n$");
        Matcher m = p.matcher(message);
        if (!m.matches()) throw new IOException("RFB Protocol violation.");
       
        System.err.printf("Client version: %d.%d\n",
                Integer.valueOf(m.group(1)), Integer.valueOf(m.group(2)));

    }

}

========== end of RFBSession.java ==========

まずは RFB の入出力ストリームを作成している。
まあ、この時点では役に立たないのだが今後のためだ。

まずは、サーバのバージョンを適当に返してみる。
現在の RFB 仕様は、3.3, 3.7, 3.8 しか定義していないが、
ここでは 4.99 という未来のバージョンにしてみた。
RFB に準拠したクライアントならば、
これを無効とせずに、低いバージョンを返すはずだ。

データを送信後は、OutputStream#flush を忘れないこと。
下位層がバッファしている可能性もあるため、
ここでデータを確実に送り出しておく。

サーバがバージョンを送信後はクライアントが返事を返す。
12 バイト読み込んで us-ascii 文字列に戻した後、
正規表現でマッチングして結果を出力しておく。

処理中に失敗した場合はとりあえず IOException とする。

次に、RFBSession を VNCSession から呼び出し、
サーバが動作するように書き換える。

========== VNCSession#run ==========

    /**
     * @see java.lang.Runnable#run()
     */
    public void run() {

        try {
           
            InputStream in = socket.getInputStream();
            OutputStream out = socket.getOutputStream();
           
            new RFBSession().execute(in, out);
           
        } catch (Exception e) {

            e.printStackTrace(System.err);

        } finally {

            try {
                socket.close();
            } catch (IOException e) {
                // 例外無視
            }
           
        }

    }

========== end of VNCSession#run ==========

さて、サーバをデバッグ実行し、
RealVNC を使って localhost に接続してみよう。

例によってクライアント側では予期せぬ切断であるため、
「The connection closed unexpectedly」のエラーとなるが、
デバッグ中の Eclipse のコンソールには、
「Client version: 3.8」等と表示されるはずだ。
これはバージョンの交渉が成功した事を意味する。

次に、サーバのバージョンを、2.99 等の低い値にしてみる。

    out.write("RFB 002.099\n".getBytes("us-ascii"));

ではデバッグ実行して接続してみよう。
今度は、クライアントが理解できないバージョンなので、
切断されてサーバ側が IOException を受取った。

また、RealVNC 側は以下のようなメッセージを表示した。
「Server gave unsupported RFB protocol version 2.99」

よし、ちゃんと動いているようである。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.