系列解析網状(上)は、共通ソースデコーダ

Fanger魏コードまたは次のスキャンの公共マイクロチャンネル番号を検索するには菜鸟飞呀飞、あなたは、マイクロチャネルのパブリック番号に焦点を当てるより読むことができるSpring源码分析、とJava并发编程の記事。

マイクロチャンネル公衆数

序文

前の記事、唯一のTCPコーデックスティックパッケージ、問題の半分パックによって解決され、デコーダはデータをデコードする方法の詳細な分析がないかネッティー分析では、本論文では、これらの特定が今日デコーダを分析しますそれは動作します。

ネッティーは、いくつかの非常に一般的なデコーダを提供してくれます、次の表に示すように、これらのデコーダは、上から下まで、これらのデコーダ難易度に応じて、ほぼすべての私たちのシーンを満たすことができます。

FixedLengthFrameDecoder(基于固定长度的解码器)
LineBasedFrameDecoder (基于行分隔符的解码器)
DelimiterBasedFrameDecoder (基于自定义分割符的解码器)
LengthFieldBasedFrameDecoder (基于长度字段的解码器)
复制代码

最初の3つのデコーダは、最後の1デコーダが簡単に理解することは、比較的複雑ではありませんが、理解しやすい、比較的簡単ですが、それはほとんどの満足なシーンです。最初の三つのデコーダは比較的単純であるため、今日、この記事の主な内容であり、分析の資料、にそれらのソースからです。最後に、デコーダのソースコードは別途後で資料を分析します。

FixedLengthFrameDecoder

クラス名によると、あなたはそれがどういう意味、この翻訳は固定長復号化に基づいていることを知ることができるのだろうか?この初期設定では、デコーダ、int型の指定された数である:フレーム長は、バック復号時に、毎回読み出すフレーム長データ・オブジェクトに復号バイト長。例えば:私たちは固定長復号指定した場合、送信者は、A、BC、DEFG、HI、9バイトの合計、それぞれ、データを4回送信した場合。フレーム長= 3、それが3の溶液は、コードあたりのバイト数を意味します、その結果がデコードされた結果:ABCDEFGHI

+---+----+------+----+          +-----+-----+-----+
| A | BC | DEFG | HI |   ->    | ABC | DEF | GHI |
+---+----+------+----+          +-----+-----+-----+
复制代码

レングスデコーダベースのソースコードやコメントを修正以下の通りである、短い答えを比較、分析するために行われ、ソースコード内のコメントを参照していません。

public class FixedLengthFrameDecoder extends ByteToMessageDecoder {

    // 表示每次解码多长的数据
    private final int frameLength;

    public FixedLengthFrameDecoder(int frameLength) {
        checkPositive(frameLength, "frameLength");
        // 指定每次解码的字节数
        this.frameLength = frameLength;
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 解码
        Object decoded = decode(ctx, in);
        // 如果解码出来的数据对象不为空,就将其保存到out这个集合中
        if (decoded != null) {
            out.add(decoded);
        }
    }

    protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        // 如果可读取的数据小于每次解码的长度,那就直接返回null
        if (in.readableBytes() < frameLength) {
            return null;
        } else {
            // 读取指定长度的字节数据,然后返回
            return in.readRetainedSlice(frameLength);
        }
    }
}
复制代码

LineBaseFrameDecoder

LineBaseFrameDecoder基づいて行デコーダデリミタ。それは何を意味するのでしょうか?読み取られるたびに行区切り(\ nまたは\ R \ n)は、それがデータオブジェクトを解析します。例えば、以下の例のように。

+---+-------+----------+------+          +-----+-----+-------+
| A | B\nC | DE\r\nFG | HI\n |   ->      | AB  | CDE | FGHI |
+---+-------+----------+------+          +-----+-----+-------+
复制代码

上記の例では、LineBaseFrameDecoderの原則はシンプルに見えるが、実際には、インプリメンテーションではそれほど単純では上記のように動作しませんでした。LineBaseFrameDecoder非常に重要ないくつかのメンバ変数を定義します。下図のように。

// 解码的最大长度
private final int maxLength;

// 当通过换行符读出来的数据超过maxLength规定的长度后,是否立即抛出异常。true表示立即
private final boolean failFast;

// 解析数据时是否跳过换行符\r\n或者\n,true表示跳过,false表示不跳过
private final boolean stripDelimiter;

// 当超过maxLength的长度后,就不能解码,需要丢弃数据,此时会将discarding设置为true,表示丢弃数据
private boolean discarding;

// 记录已经丢弃了对少字节的数据
private int discardedBytes;

// 最后一次扫描的位置
private int offset;
复制代码

データがデコードされると、最初の検索があろう改行位置を、その後、改行位置の長さにリードポインタの現在の位置から計算される長さよりも大きい場合、maxLengthの、それが無効なデータを復号することができないことを意味し、それは廃棄する必要があります。例えば、我々は、設定のmaxLength = 4、次の図に示されている例を2つだけが正しいデータパケットを復号化:ABCDEF、そしてためGHIJBCA、その長さにわたって、6であるため、maxLengthの所定の長さ、したがって、廃棄されました。

例示的な行デコーダ

デコードされたデータは、デコードされたデータは、によって、改行\ rを\ nまたは\ N-後の保持のためになるとstripDelimiterの属性制御、真の手段スキップ改行は、デコードされたデータは、改行を保持されません。また、改行は、データ長によって読み出さ以上のときmaxLengthの後に、あなたがして、データが時に破棄され、データを廃棄する必要がありますか?すぐにデータを破棄しますか?それを読んだとき、次のデータが破棄されるまで、または待つのか?これがかもしれFAILFAST真はすぐに捨て表し、特性を制御します。あなたはデータを破棄する必要がある場合一方、それがされる廃棄真である性質を。

デコーダルック源復号処理に関連して以下に改行。前の記事が挙げデコーダ抽象クラス継承デコーダ改行ByteToMessageDecoderは、抽象メソッド書き換えデコード()

protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    // 解码
    Object decoded = decode(ctx, in);
    // 能解码出来数据,就将解码出来的结果存放到out中
    if (decoded != null) {
        out.add(decoded);
    }
}
复制代码

これは、別のオーバーロードコアロジックに見ることができるデコード()メソッド。このオーバーロードされたメソッドのソースは、次のように、それは少し、全体的なフレームワークを注文します、非常に長いです。

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    // 返回\n或者\r\n的下标位置
    final int eol = findEndOfLine(buffer);
    if (!discarding) {
        // 找到换行符
        if (eol >= 0) {
            // 解码...
            return frame;
        } else {
            // ...
            return null;
        }
    } else {
        // 找到了换行符
        if (eol >= 0) {
            // ...
        } else {
            // ...
        }
        return null;
    }
}
复制代码

まず、\ nまたは\ R \ nは添字位置を探し、検索プロセスが比較的単純で、バイトの配列をトラバースすることです。見つかった場合、ラインフィードが見つからない場合は、0以上の値が返され、改行は、それがより大きな数を返すか、0に等しいであろう。\ r個のインデックス値を返し、その後、実測値は\ Rであれば\ nは、請求場合\ nは、その後、Nインデックス値\返します。

次に、論理の残りの部分は、二つの部分に分けることができる:ドロップモードです最初の部分、すなわち、モードを破棄で実行される論理ではない:falseに廃棄=!、そして真として破棄、第二の部分はドロップモードロジックで行われます。:二つの部分に関しては、各部分が2例に分けることができ改行を見つけるために、そして改行を見つけられませんでしたので、ここでは、実際にロジックの4種類です。場合メソッドを復号デコーダ最初の呼び出し、その中の場合廃棄= falseに非ドロップモードで、すなわち、。

最初のケース:ノンドロップモードと見出さ改行(EOL> = 0)次のように、特定のコードに、この場合の対応が実行されます。改行の間の長さのデータ長に第一リードポインタを算出し、このmaxLengthのデータの長さが限界を超えているか否かを判断し、超えた場合、このデータを廃棄する必要があり、データが違法であることを示しています。どのようにそれを破棄するには?読み取りポインタがByteBufの改行に移動させた後、その後、呼び出しが失敗()メソッド、例外処理。データが上限を超えていない場合は、データが正当であると正しく復号することができることを意味します。次に、復号化、ベース時stripDelimiter改行、最後の復号されたフレームに割り当てられたデータ、およびリターンを保持するかどうかを決定します。この状況は、理想的な状況です。

// 非丢弃模式且找到换行符
if (eol >= 0) {
    final ByteBuf frame;
    // 计算出要截取的数据长度
    final int length = eol - buffer.readerIndex();
    // 判断是\r\n还是\n,如果是\r\n返回2,如果是\n返回1
    final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;

    // 如果读取的数据长度,超过最大长度,那么就不能读这一段数据,需要跳过这段数据,即将读指针直接\n之后。
    if (length > maxLength) {
        // 跳过这段数据
        buffer.readerIndex(eol + delimLength);
        // 进入失败模式
        fail(ctx, length);
        return null;
    }

    // 是否跳过换行符
    if (stripDelimiter) {
        // 读数据时不读取换行符
        frame = buffer.readRetainedSlice(length);
        // 跳过换行符
        buffer.skipBytes(delimLength);
    } else {
        // 读取到的数据包含换行符
        frame = buffer.readRetainedSlice(length + delimLength);
    }
    return frame;
}
复制代码

第二の場合、ノンドロップモードが、改行(EOL <0)が見つかりません次のように実行される特定のコードに、この場合に相当します。改行が見つからなかったので、それが復号されたデータのうち、確かではありません。しかし、私たちは現在のバッファ読み取り可能なデータは、この上限を超えたかを判断するために、この場合の必要性には、maxLengthの制限がありますので。もしそうであれば、それは確かに、このデータので、不正なデータではない、すべてのそれは低下したときに必要性が、廃棄しますか?今では失われたか、データを復号化するために、すぐに次の破棄しますか?メンバ変数フェイルファーストの値に応じて、真、偽を復号廃棄さ次回によって表され、直ちに破棄表します。

else {
    // 没有找到换行符,则判断可读的数据长度是否超过最大长度,如果超过,则需要丢弃数据
    final int length = buffer.readableBytes();
    if (length > maxLength) {
        // 设置丢弃的长度为本次buffer的可读取长度
        discardedBytes = length;
        // 修改读指针,跳过这段数据
        buffer.readerIndex(buffer.writerIndex());
        // 设置为丢弃模式
        discarding = true;
        offset = 0;
        // 是否快速进入丢弃模式
        if (failFast) {
            fail(ctx, "over " + discardedBytes);
        }
    }
    return null;
}
复制代码

第三の場合、パターンをドロップし、ラインフィード(EOL> = 0)が見つかりました。次のように、対応するコードです。表面は、破棄モードに入るときに、現在のデータを破棄するよう前記復号サイクルは親クラスと呼ばれている結果は、()メソッドデコードをデコードサブクラスであろう。この時点ではあるが、データの新しいラインは、この中で見つかったすべてを破棄します前に必要性が、ので、この場合には、廃棄することを以前のデータ(データの最初のサイクルを含むが、廃棄する必要がある)ことから、改行を検索すると、最終的には決意を正常に復号化されるときに、データが、次の読み出しサイクルの後に廃棄されているため、廃棄モードは、falseに設定されています。

// 丢弃模式且找到了换行符
if (eol >= 0) {
    // 以前丢弃的数据长度+本次可读的数据长度
    final int length = discardedBytes + eol - buffer.readerIndex();
    // 拿到分隔符的长度
    final int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1;
    // 跳过丢弃的数据
    buffer.readerIndex(eol + delimLength);
    // 设置丢弃数据长度为0
    discardedBytes = 0;
    // 设置非丢弃模式
    discarding = false;
    if (!failFast) {
        fail(ctx, length);
    }
}
复制代码

第四に、滴下パターンとラインブレーク(EOL <0)が見つからない、次のように、対応するコードです。何の改行がありませんが発見されたため、この時点では、それは確かに正しく復号することができない、ともドロップモードである、この読み出されたデータはすべて無効と破棄する必要があるので、コードのこのセクションでは、我々が発見し、ノーデータはすぐに破棄され、なぜですか?でも破棄はデータの前半の読み取りに落ちたので、データが破棄される場合、我々はそれを取るますので、その後、次の読み出しデータは、データ長が、あまりのmaxLengthを指定した長さよりも見つけることができデコードに、実際には、このデータは利用できません。

else {
    // 没找到换行符
    // 以前丢弃的数据 + 本次所有可读的数据
    discardedBytes += buffer.readableBytes();
    // 跳过本次所有可读的数据
    buffer.readerIndex(buffer.writerIndex());
    // 我们跳过缓冲区中的所有内容,需要再次将偏移量设置为0。
    offset = 0;
}
复制代码

それは第三のケース又は第4のケースであるかどうか、ドロップモードでは、正しく復号できず、最終的なリターンはヌル、すなわち、無復号の目的です。

前述の分析から、それはいくつかのオーバーロードされたメソッドのデータが破棄されるとき、呼び出しが失敗()メソッドを、見ることができますが、コールは最終的に過負荷に以下のだろう。

private void fail(final ChannelHandlerContext ctx, String length) {
    ctx.fireExceptionCaught(
            new TooLongFrameException(
                    "frame length (" + length + ") exceeds the allowed maximum (" + maxLength + ')'));
}
复制代码

実際に、私たちは頻繁にしてパイプラインによって異常ダウンこれを広め、見ることができる実用的なアプリケーションでの例外情報は、例外を作成することで、最終的にハンドラの呼び出しexceptionCaught()メソッド。

全体的に言えば、LineBaseFrameDecoderデータの数で割った場合の簡単なアイデアを実装するために、改行に基づくデコーダは、N- R \に従ってデータを分割することで、\ nまたは\その後廃棄されたデータ、所定の最大長さよりも大きいです、または復号化が成功したこと。

DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoderがユーザーは次のように定義されている場合、ユーザー自身の指定された区切り文字、区切りに従ってデコードするデコーダベースのセパレータであるセミコロン(;)は、セミコロンに従ってデータを復号化することを意味します。さらに、ユーザは、セパレータのいずれかを打つ限り、データが読み出されるように、区切り文字を複数指定することができ、それは一旦復号化することができます。例えば、以下に示す、デリミタの指定されたカンマ、感嘆符、セミコロン、改行:次いで、デコードの結果ABCDEFAghiBCA

ベースセパレータ例デコーダ

さらにのみならあれば、2つだけは、区切り文字を指定し、そして\ rと\ nおよび\ nは、デコーダ系セパレータはラインベースのデコーダなります。次に、復号時に、ラインスプリッタの直接使用LineBaseFrameDecoderのデコード。

DelimiterBasedFrameDecoderこれらの意味および使用のいくつかの非常に重要な特性のプロパティ定義LineBaseFrameDecoderのほぼ同じで定義されたメンバ変数デコーダ。ここでの意味と、これらのメンバ変数の効果があります。

// 分隔符数组,因为可以同时制定过个分隔符,所以使用数组来存放
private final ByteBuf[] delimiters;

// 最大长度限制
private final int maxFrameLength;

// 是否跳过分隔符,true表示跳过
private final boolean stripDelimiter;

// 是否立即丢弃,true:立即
private final boolean failFast;

// 是否处于丢弃模式
private boolean discardingTooLongFrame;

// 累计丢弃的字节数
private int tooLongFrameLength;

/** Set only when decoding with "\n" and "\r\n" as the delimiter.  */
// 如果分隔符是\r\n和\n时,就直接使用基于行的解码器
private final LineBasedFrameDecoder lineBasedDecoder;
复制代码

これは、と見ることができるLineBaseFrameDecoderのセパレータはデコーダ変数の2人のメンバー、一つを有することを除いてデコーダデリミタの複数のユーザがカスタマイズできるため、店舗ユーザー定義の区切り文字に使用される配列である属性を、セパレータ、ストアへのアレイの使用。別のはlineBasedDecoderに、セパレータはデコーダローデコーダとなり、ロウデコーダによって表される属性は、計算され、必要な区切りとIFFデリミタアレイが\ rと\ nおよび\ nはプロパティの値LineBaseFrameDecoderは、コンストラクタを初期化。ソースは以下のとおりです。

public DelimiterBasedFrameDecoder(
        int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {

    // 省略其他代码...
    if (isLineBased(delimiters) && !isSubclass()) {
        lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
        this.delimiters = null;
    } else {
        // 省略其他代码...
        lineBasedDecoder = null;
    }
    // 省略其他代码...
}
复制代码

構成プロセスでは、我々が通過するisLineBased(デリミタ)メソッドは、区切り文字と判定されたかどうか\ R \ nおよび\ N-、ロウデコーダを作成し、その後に割り当てられたその場合lineBasedDecoderの属性、そうでなければ作るlineBasedDecoderのプロパティが空です。isLineBased()次のようにソース方法があります。

private static boolean isLineBased(final ByteBuf[] delimiters) {
    // 当分隔符数组中只包含\r\n和\n时,才返回true
    if (delimiters.length != 2) {
        return false;
    }

    ByteBuf a = delimiters[0];
    ByteBuf b = delimiters[1];
    // 保证令a = \r\n,令b= \n
    if (a.capacity() < b.capacity()) {
        a = delimiters[1];
        b = delimiters[0];
    }
    return a.capacity() == 2 && b.capacity() == 1
            && a.getByte(0) == '\r' && a.getByte(1) == '\n'
            && b.getByte(0) == '\n';
}
复制代码

isLineBased()がデコーダロウデコーダにセパレータに基づいて、ある場合には区切り文字がある場合にのみ\ R \ nおよび\時刻n、それは真を返し、ソース方法を見ることができます。

同様に、セパレータを書き換えるデコーダ)(親クラスの抽象メソッドを復号します。

protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    // 调用decode()的重载方法解码
    Object decoded = decode(ctx, in);
    // 如果能成功解码,就添加到out中
    if (decoded != null) {
        out.add(decoded);
    }
}
复制代码

オーバーロードされたコアロジックにデコード(CTX、IN)の方法。プロセスの同じソースは、実質的に骨格を次の、私は読みやすくするため、コードの変更は軽微だった、合理化された、非常に長いです。

protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
    // 首先判断基于行的解码器是否被初始化了,如果被初始化了,就表明分隔符就是\r\n和\n,直接只用行解码器进行解码
    if (lineBasedDecoder != null) {
        return lineBasedDecoder.decode(ctx, buffer);
    }
    int minFrameLength = Integer.MAX_VALUE;
    ByteBuf minDelim = null;
    // 遍历所有的分隔符,然后找到最小位置的分割符
    for (ByteBuf delim: delimiters) {
        // 找到最小分隔符的位置
    }
    // 如果找到了分割符
    if (minDelim != null) {

        //如果处于丢弃模式
        if (discardingTooLongFrame) {

            return null;
        }else{
            return frame;
        }
    } else {
        //如果没有找到分割符
        //判断是否处于非丢弃模式
        if (!discardingTooLongFrame) {

        } else {

        }
        return null;
    }
}
复制代码

最初は、決定lineBasedDecoderは、それがセパレータは\ R \ nおよび\ N-直接デコードするデコーダ、その行であることを意味し、空でない場合、空であり、そうでない場合、論理の背後に進みます。

デリミタの複数指定することができるので、ロウデコーダの以前の分析は、スプリットシンボルデコーダであるとは異なり、我々読み取り可能なデータを見つける最初の必要性は、デリミタ、および出現する第1表示しますどの位置に、どのようにそれを見つけるには?各セパレータを介して、そしてそれらは、索引可読データの最後のLOのデリミタが見出された最小のインデックスデリミタの最初の発生であったです、、。

行デコーダとほぼ同じだけでなく、第2例の背後にあるロジックは:セパレータを見つけるためにセパレータを再生するために見ていません次いで、フロントケースの各々が細分化されたため、ドロップモードであるので、4つのケースの合計。行デコーダは、まずモードが破棄されたか否かを判断し、点を除いて、行デコーダはデリミタは、見つかった一貫性のあるロードマップか否かを判定する。まだのみセパレータを見つけ、デコーダは、それ以外の場合はNULLを返し、データを復号するために廃棄モードありません。内側の具体的な詳細は、説明は、同じ行デコーダの前分析を開始しないであろう。

概要

固定長デコーダ、列デコーダ、セパレータに基づくデコーダ三個の比較的単純なデコーダ、結合記事:続い記事資料は、3つの一般的なデコーダで言及した資料に分析されます写真および実施例は、それが理解しやすく、実際には、我々は通常の開発に使用str.spilt()メソッドの原則は、廃棄モード裁判官する必要があることを除いて、同様です。次の記事は分析するフィールドの長さに基づいてデコーダ今日、デコーダ分析をもう少し3つのデコーダよりも少し複雑であってもよいが、それは最も汎用性の高いデコーダであるだろう。

勧告

マイクロチャンネル公衆数

おすすめ

転載: juejin.im/post/5e0a16e4e51d4575d434e363