2007 年 1 月 10 日 23 時 56 分

SetEncodings メッセージ


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


今日は SetEncodings メッセージだ。
これは種類 2 番のクライアントメッセージで、
クライアントが対応している画面データの符号化方式を、
サーバーに通知するために送られる。

RFB では、画面のデータを送る際の通信量を削減するために、
いくつもの符号化方式(圧縮形式等)を定めている。

符号化方式は多種多様であり、その内容は複雑である。
それら全てに対応するにはそれ相応のコード量が必要だ。

そのため、クライアントはその全てに対応する必要はなく、
自身が扱える符号化形式のみをサーバに通知することで、
サーバが送ってくる画面データの符号化を、
一部の方式に限定することができると言うわけだ。

このメッセージは可変長で、以下のような構造を持つ。

    U8 messageType; // 常に 2
    U8 reserved; // 1 バイトの詰め物
    U16 count; // 対応している符号化方式の数
    S32 types[]; // 対応している符号化方式の配列

符号化方式には一意のコードが割り当てられており、
types 配列には、クライアントの対応する符号化方式が入る。
types は可変なので、count にはその要素数を格納する。

では、最初に符号化方式を表す定数を定義しよう。

========== Encoding.java ==========

package jp.loafer.rfb;

/**
* 符号化方式の定数。
* @author kes
*/
public final class Encoding {

    /**
     * 符号化なし。
     */
    public static final int RAW = 0;

    /**
     * Copy rectangle 符号化。
     */
    public static final int COPY_RECT = 1;

    /**
     * Rise-and-run-length 符号化。
     */
    public static final int RRE = 2;

    /**
     * Hex-tile 符号化。
     */
    public static final int HEXTILE = 5;

    /**
     * Zlib Run-length 符号化。
     */
    public static final int ZRLE = 16;

    /**
     * カーソル擬似符号化。
     */
    public static final int PSEUDO_CURSOR = -239;

    /**
     * サイズ変更擬似符号化。
     */
    public static final int PSEUDO_DESKTOP_SIZE = -223;

    private Encoding() {
        // インスタンスは不要
    }

}

========== end of Encoding.java ==========

見慣れぬ名前がずらずらと並んでいるが、
現時点では定数値が分かればそれでいいので気にしない。

次に、SetEncodings メッセージのクラスを作ろう。

=========== SetEncodingsMessage.java ===========

package jp.loafer.rfb.message.client;

import java.io.IOException;

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

/**
* SetEncodings クライアントメッセージ。
* @author kes
*/
public class SetEncodingsMessage extends BaseClientMessage {
   
    /**
     * 永続化用既定コンストラクタ。
     */
    public SetEncodingsMessage() {
        //
    }

    /**
     * SetEncodings メッセージを作成。
     * @param encodingTypes 対応する符号化方式の配列。
     */
    public SetEncodingsMessage(int[] encodingTypes) {
        if (encodingTypes == null) throw new NullPointerException();
        this.encodingTypes = encodingTypes;
    }

    /**
     * 符号化方式の配列を取得。
     * @return {@link Encoding}.XXX 符号化方式定数の配列。
     */
    public int[] getEncodingTypes() {
        return encodingTypes.clone();
    }
    /**
     * 符号化方式の配列の要素を取得。
     * @param index
     * @return {@link Encoding}.XXX 符号化方式定数。
     */
    public int getEncodingTypes(int index) {
        return encodingTypes[index];
    }

    /**
     * @see ClientMessage#getType()
     */
    public int getType() {
        return ClientMessage.SET_ENCODINGS;
    }

    /**
     * @see ClientMessage#read(RFBContext, RFBInputStream)
     */
    @Override
    public void read(RFBContext context, RFBInputStream in) throws IOException {
        super.read(context, in);
        in.readU8();
        int length = in.readU16();
        encodingTypes = new int[length];
        for (int i = 0; i < length; ++i) {
            encodingTypes[i] = in.readS32();
        }
    }

    /**
     * @see ClientMessage#write(RFBContext, RFBOutputStream)
     */
    @Override
    public void write(RFBContext context, RFBOutputStream out) throws IOException {
        super.write(context, out);
        out.writeU8(0);
        out.writeU16(encodingTypes.length);
        for (int i = 0; i < encodingTypes.length; ++i) {
            out.writeS32(encodingTypes[i]);
        }
    }

    private int[] encodingTypes;

}

========== end of SetEncodingsMessage.java ==========

SetEncodings クラスは、
配列をフィールドとして持つ設計になるが、
それを getter で公開する際には注意が必要だ。

Java の配列はオブジェクトであり、
その要素は変更が可能である。
もし getter が配列型のフィールドをそのまま返すと、
呼び出し側で配列を操作することで、
フィールドの配列を直接編集できてしまうことになる。

SetEncodings クラスも不変とするためには、
getter では配列をコピーして返す必要がある。

また、配列型のフィールドを公開する場合は、
インデックス付きの getter を用意する手もある。
この場合、配列の要素のみを取得できるので、
配列全体をコピーする必要がなくなるのだ。



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