SpringBoot + Netty 實現 Json字符串 的傳輸(三)

網絡通信從編解碼開始,前面的第一篇文章中,介紹過數據包的結構,這篇文章就要介紹一下拆包和組包的過程。

1. 包頭字段的設計目的

    A. 起始分隔符:標明一個數據包的開始部分(裏面還隱含了小端模式的信息,這個小端模式可以忽略);

    B. 協議的版本:當前版本是明文傳輸的,考慮到後期升級可能要採用密文傳輸包體,所以,設計了一個版本字段,當然,也可以用於協議內容的擴充,添加新的數據包類型時增加版本號的即可;

    C. 頻道號:這個字段的作用不明顯,目前是想將包信息按照業務分類,比如:網絡連接,權限認證,聊天信息等;

    D. 命令字:與頻道號、版本號聯合起來作爲包體類型的ID,參與Json串和Java對象的轉換操作;

2. 包體

    要傳輸的Json字符串,二進制的形式,採用UTF-8的編碼形式;

3. 包尾

    結束分隔符:標明一個數據包的結束部分,如果按照包長度解析錯誤之後,可以通過結束分隔符丟棄碎包,目前Demo沒有完成這個功能。

package houlei.net.tcp.codec;

import houlei.net.tcp.pkg.PackageVersion;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;

public class GenericPackageCodec extends ByteToMessageCodec<GenericPackage> {

    public static final int HEADER_TAIL_LENGTH = 16;
    public static final int PKG_MAX_LENGTH = 2048;

    // 編碼過程
    @Override
    protected void encode(ChannelHandlerContext ctx, GenericPackage pkg, ByteBuf out) throws Exception {
        switch (PackageVersion.valueOf(pkg.getVersion())) {
            case V10:
                encodeV10(ctx, pkg, out);
                return;
            default:
                encodeVX(ctx, pkg, out);
                return;
        }

    }

    private void encodeVX(ChannelHandlerContext ctx, GenericPackage pkg, ByteBuf out) {
        // 其他版本的編碼過程沒有實現,默認V10版本的處理過程
        encodeV10(ctx, pkg, out);
    }

    private void encodeV10(ChannelHandlerContext ctx, GenericPackage pkg, ByteBuf out) {
        String data = pkg.getData();
        byte[] body = data==null ? new byte[0] : data.getBytes(Charset.forName("UTF-8"));
        pkg.setLength((short)body.length);

        if (out.isWritable((HEADER_TAIL_LENGTH + body.length))) {
            out.writeBytes(GenericPackage.PKG_PREFIX);
            out.writeShort(pkg.getVersion()).writeShort(pkg.getChannel()).writeShort(pkg.getCommand()).writeShort(body.length);
            out.writeBytes(body);
            out.writeBytes(GenericPackage.PKG_SUFFIX);
            ctx.flush();
        }
    }

    // 解碼過程
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        while (in.isReadable(HEADER_TAIL_LENGTH)) {
            in.markReaderIndex();
            byte[] buffer = new byte[GenericPackage.PKG_PREFIX.length];
            in.readBytes(buffer);
            if (!Arrays.equals(GenericPackage.PKG_PREFIX, buffer)) {
                // 碎包片段。不進行skip,直接拋出異常,關閉連接。
                throw new CodecException("PKG_PREFIX error.");
            }
            short version = in.readShort();
            short channel = in.readShort();
            short command = in.readShort();
            short length  = in.readShort();
            if (length < 0 || length > PKG_MAX_LENGTH) {
                // 長度不能小於零, 也不能超過最大長度。
                throw new CodecException("PKG_length error.");
            }
            if (!in.isReadable(length + GenericPackage.PKG_SUFFIX.length)) {
                in.resetReaderIndex();
                // 接收到了不完整的包,等待下次讀取。
                //TODO 應該校驗接收緩衝是否超過最大包長度。
                return;
            }
            byte[] body = new byte[length];
            in.readBytes(body);
            in.readBytes(buffer);
            if (!Arrays.equals(GenericPackage.PKG_SUFFIX, buffer)) {
                // 碎包片段。未找到包尾分隔符。不進行skip,直接拋出異常,關閉連接。
                throw new CodecException("PKG_SUFFIX error.");
            }

            in.resetReaderIndex();
            in.skipBytes(HEADER_TAIL_LENGTH + length);
            switch (PackageVersion.valueOf(version)) {
                case V10:
                    decodeV10(ctx, version, channel, command, body, out);
                    break;
                default:
                    decodeVX(ctx, version, channel, command, body, out);
                    break;
            }

        }
    }

    private void decodeVX(ChannelHandlerContext ctx, short version, short channel, short command, byte[] body, List<Object> out) {
        // 其他版本的解碼過程沒有實現,默認V10版本的處理過程
        decodeV10(ctx, version, channel, command, body, out);
    }

    private void decodeV10(ChannelHandlerContext ctx, short version, short channel, short command, byte[] body, List<Object> out) {
        String data = new String(body, Charset.forName("UTF-8"));
        GenericPackage pkg = new GenericPackage();
        pkg.setVersion(version);
        pkg.setChannel(channel);
        pkg.setCommand(command);
        pkg.setLength((short)body.length);
        pkg.setData(data);
        out.add(pkg);
    }

}
package houlei.net.tcp.codec;

import houlei.net.tcp.pkg.PackageFactory;
import houlei.net.tcp.pkg.PackageType;
import houlei.net.tcp.utils.JsonUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

@Component
public class GenericPackageClassifierCodec extends MessageToMessageCodec<GenericPackage, Object> {

    @Resource
    private PackageFactory packageFactory;

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
        PackageType type = packageFactory.getPackageType(msg.getClass());
        if (type != null) {
            String json = JsonUtil.toString(msg);
            GenericPackage genericPackage = new GenericPackage();
            genericPackage.setVersion(type.getVersion());
            genericPackage.setChannel(type.getChannel());
            genericPackage.setCommand(type.getCommand());
            genericPackage.setData(json);
            out.add(genericPackage);
        } else {
            //TODO 未知類型的對象,無法進行編碼操作,應該拋出異常。
        }
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, GenericPackage pg, List<Object> out) throws Exception {
        Class<?> klass = packageFactory.getClass(pg.getVersion(), pg.getChannel(), pg.getCommand());
        if (klass != null) {
            String json = pg.getData();
            Object pkg = JsonUtil.fromString(json, klass);
            out.add(pkg);
        } else {
            //TODO 未知類型的對象,無法進行解碼操作,應該進行特殊處理。
        }
    }

}
package houlei.net.tcp.codec;

import houlei.net.tcp.pkg.PackageType;
import houlei.net.tcp.utils.JsonUtil;

public class GenericPackage {

    public final static byte[] PKG_PREFIX = new byte[]{(byte)0xFF,(byte)0xFE,0x06,0x08};
    public final static byte[] PKG_SUFFIX = new byte[]{(byte)0xFF,(byte)0xFE,0x08,0x06};

    private short version;
    private short channel;
    private short command;
    private short length;
    private String data;

    public GenericPackage(){}
    public GenericPackage(PackageType type) {
        this.version = type.getVersion();
        this.channel = type.getChannel();
        this.command = type.getCommand();
    }

    @Override
    public String toString() {
        return JsonUtil.toString(this);
    }

    public short getVersion() {
        return version;
    }

    public void setVersion(short version) {
        this.version = version;
    }

    public short getChannel() {
        return channel;
    }

    public void setChannel(short channel) {
        this.channel = channel;
    }

    public short getCommand() {
        return command;
    }

    public void setCommand(short command) {
        this.command = command;
    }

    public short getLength() {
        return length;
    }

    public void setLength(short length) {
        this.length = length;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章