Netty 自定义编码、解码器案例

Netty内置了很多编码、解码器,例如MsgPack、Protobuf,这些二进制编码器的性能都非常高,远远高于Java内置的序列化、反序列化的性能

但是出于各种原因,我们有时候需要自定义编码、解码,一般做法都是将消息分为几段(消息头初始标记、消息体长度标识、消息校验Token、消息体内容),通过预定义的各个分段的长度直接获取进行匹配校验

Netty也提供了各种扩展口给到上层应用自行扩展,下面就是一个Demo案例

 

定义message类,消息的主类

package netty.customcoding;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;

import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
@Data
@ToString
@AllArgsConstructor
public class Message {
    // 协议头
    private MessageHead head;

    // 内容
    private byte[] content;

    /**
     * <p>Title:生成校验Token</p>
     * <p>Description:</p>
     *
     * @return java.lang.String
     * @throws
     * @author QIQI
     * @params []
     * @date 2021/04/07 22:01
     */
    public String buidToken() throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance( "MD5" );
        //生成token
        String allData = String.valueOf( this.getHead().getHeadData() );
        allData += String.valueOf( this.getHead().getLength() );
        allData += new String( this.getContent() );
        allData += "hello world";//秘钥
        md.update( allData.getBytes() );
        byte[] digest = md.digest();
        return DatatypeConverter
                .printHexBinary( digest ).toUpperCase();

    }

    /**
     * <p>Title:验证是否认证通过</p>
     * <p>Description:</p>
     *
     * @return boolean
     * @throws
     * @author QIQI
     * @params [token]
     * @date 2021/04/07 22:02
     */
    public boolean authorization(String token) {
        //表示参数被修改
        if (!token.equals( this.getHead().getToken() )) {
            return false;
        }
        return true;
    }
}

消息头类,主要标识消息开始前缀、消息体长度、消息校验令牌 

package netty.customcoding;


import lombok.Data;
import lombok.ToString;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
@Data
@ToString
public class MessageHead {
    private int headData = 0X76;//协议开始标志
    private int length;//包的长度
    private String token;
}

消息编码器,写入相关消息信息 

package netty.customcoding;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
public class MsgPackEncoder extends MessageToByteEncoder<Message> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, Message msg, ByteBuf out) throws Exception {
        byte[] tokenByte = new byte[50];
        // 写入开头的标志
        out.writeInt( msg.getHead().getHeadData() );
        // 写入包的的长度
        out.writeInt( msg.getContent().length );
        byte[] indexByte = msg.getHead().getToken().getBytes();
        System.arraycopy( indexByte, 0, tokenByte, 0, indexByte.length > tokenByte.length ? tokenByte.length : indexByte.length );
        //写入令牌
        out.writeBytes( tokenByte );
        // 写入消息主体
        out.writeBytes( msg.getContent() );
    }
}

 消息解码器,根据定义好的各种长度消息前缀、消息内容字节、消息令牌字节

package netty.customcoding;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.text.SimpleDateFormat;
import java.util.List;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
public class MsgPackDecoder extends ByteToMessageDecoder {
    private final int BASE_LENGTH = 4 + 4 + 50;//前缀4个字节,消息体长度4个字节,令牌50个字节
    private int headData = 0X76;//协议开始标志

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
        int beginIndex;//记录包开始位置
        while (true) {
            // 获取包头开始的index
            beginIndex = buffer.readerIndex();
            //如果读到开始标记位置 结束读取避免拆包和粘包
            if (buffer.readInt() == headData) {
                break;
            }
            //初始化读的index为0
            buffer.resetReaderIndex();
            //如果当前buffer数据小于基础数据 返回等待下一次读取
            if (buffer.readableBytes() < BASE_LENGTH) {
                return;
            }
        }
        // 消息的长度
        int length = buffer.readInt();
        // 判断请求数据包数据是否到齐
        if ((buffer.readableBytes() - 50) < length) {
            //没有到齐 返回读的指针 等待下一次数据到期再读
            buffer.readerIndex( beginIndex );
            return;
        }
        //读取令牌
        byte[] tokenByte = new byte[50];
        buffer.readBytes( tokenByte );
        //读取content
        byte[] data = new byte[length];
        buffer.readBytes( data );
        MessageHead head = new MessageHead();
        head.setHeadData( headData );
        head.setToken( new String( tokenByte ).trim() );
        head.setLength( length );
        Message message = new Message( head, data );
        //认证不通过
        if (!message.authorization( message.buidToken() )) {
            ctx.close();
            return;
        }
        out.add( message );
        buffer.discardReadBytes();//回收已读字节
    }
}

 netty server端代码

package netty.customcoding;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
public class MessagePackServer {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup( 1 );
        NioEventLoopGroup workGroup = new NioEventLoopGroup( 4 );
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()
                    .group( bossGroup, workGroup )
                    .channel( NioServerSocketChannel.class )
                    .option( ChannelOption.SO_BACKLOG, 1024 )
                    .childOption( ChannelOption.SO_KEEPALIVE, true )
                    .childOption( ChannelOption.TCP_NODELAY, true )
                    .handler( new LoggingHandler( LogLevel.INFO ) )
                    .childHandler( new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline channelPipeline = socketChannel.pipeline();
                            channelPipeline.addLast( new MsgPackDecoder() );
                            channelPipeline.addLast( new MsgPackEncoder() );
                            channelPipeline.addLast( new EchoServerHandler() );
                        }
                    } );
            ChannelFuture channelFuture = serverBootstrap.bind( 8899 ).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    static final class EchoServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            Message message = (Message) msg;
            System.out.println( "server read msg is:" + message );
            ctx.writeAndFlush( message );
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println( "channel active id is:" + ctx.channel().id() );
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println( "channel idle id is:" + ctx.channel().id() );
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println( cause.getMessage() );
            ctx.close();
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            System.out.println( "channelReadComplete is success" );
            ctx.flush();
        }
    }
}

netty client端代码 

package netty.customcoding;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

import java.util.Date;

/**
 * <p>Title:</p>
 * <p>Description:</p>
 *
 * @author QIQI
 * @date
 */
public class MessgaePackClient {
    public static void main(String[] args) throws Exception {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group( bossGroup )
                    .channel( NioSocketChannel.class )
                    .handler( new LoggingHandler( LogLevel.INFO ) )
                    .option( ChannelOption.SO_KEEPALIVE, true )
                    .option( ChannelOption.TCP_NODELAY, true )
                    .handler( new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel nioSocketChannel) {
                            ChannelPipeline channelPipeline = nioSocketChannel.pipeline();
                            channelPipeline.addLast( new MsgPackEncoder() );
                            channelPipeline.addLast( new MsgPackDecoder() );
                            channelPipeline.addLast( new SimpleClientHandler() );
                        }
                    } );
            ChannelFuture future = bootstrap.connect( "127.0.0.1", 8899 ).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
        }
    }

    static final class SimpleClientHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
            Message message = (Message) o;
            System.out.println( "client read is:" + message );
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
            System.out.println( throwable.getMessage() );
            channelHandlerContext.close();
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            MessageHead head = new MessageHead();
            String content = "Hello World";
            head.setLength( content.length() );
            Message message = new Message( head,content.getBytes() );
            message.getHead().setToken( message.buidToken() );
            ctx.writeAndFlush( message );
            System.out.println("client is active");
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }
    }
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝-琪琪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值