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 {

    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 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 out) {
        // 其他版本的解码过程没有实现,默认V10版本的处理过程
        decodeV10(ctx, version, channel, command, body, out);
    }

    private void decodeV10(ChannelHandlerContext ctx, short version, short channel, short command, byte[] body, List 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 {

    @Resource
    private PackageFactory packageFactory;

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, List 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 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;
    }
}

 

你可能感兴趣的:(Java,EE)