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();
}
}
}