通过扩展MessageToByteEncoder和ByteToMessageDecoder来自定义编解码器
- 原理
使用int数据类型来记录整个消息的字节数组长度,然后将该int数据作为消息的消息头一起传输
服务端接收消息数据时,先接收4个字节的int数据类型数据,此数据即为整个消息字节数组的长度,再接收剩余字节,直到接收的字节数组长度等于最先接收的int数据类型数据大小,即字节数组的长度
类似protobuf
- 消息格式
length | Message Data
- LengthFieldPrepender与LengthFieldBasedFrameDecoder原理
- 代码
继续使用Netty对Protobuf的解决粘包/半包问题的编解码方案中的UserInfo
1.CustomServer
/**
* @author pdc
*/
public class CustomServer {
public void bind(int port) throws Exception {
//配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//解决粘包/半包,根据消息长度自动拆包
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
//配置自定义序列化解码工具
ch.pipeline().addLast(new CustomV1Decoder());
//解决粘包/半包问题附加消息长度在消息头部
ch.pipeline().addLast(new LengthFieldPrepender(2));
//配置自定义序列化编码工具
ch.pipeline().addLast(new CustomV1Encoder());
ch.pipeline().addLast(new CustomServerHandler());
}
});
//绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
//等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
//优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new CustomServer().bind(port);
}
}
2.CustomServerHandler
/**
* @author pdc
*/
public class CustomServerHandler extends ChannelInboundHandlerAdapter {
private static AtomicInteger counter = new AtomicInteger(0);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接收客户端发送过来的消息
// 其中经过前面解码工具的处理
// 将字节码消息自动转换成了UserInfo对象
UserInfo req = (UserInfo) msg;
System.out.println("received from client:" + req.toString() + " counter :" + counter.incrementAndGet());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
3.CustomClient
/**
* @author pdc
*/
public class CustomClient {
public void connect(int port, String host) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
//解决粘包/半包,根据消息长度自动拆包
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
//配置自定义序列化解码工具
ch.pipeline().addLast(new CustomV1Decoder());
//解决粘包/半包问题附加消息长度在消息头部
ch.pipeline().addLast(new LengthFieldPrepender(2));
//配置自定义序列化编码工具
ch.pipeline().addLast(new CustomV1Encoder());
ch.pipeline().addLast(new CustomClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new CustomClient().connect(port, "127.0.0.1");
}
}
4.CustomClientHandler
/**
* @author pdc
*/
public class CustomClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 1000; i++) {
//channel建立之后,向服务端发送消息
// 需要注意的是这里写入的消息是完整的UserInfo对象
UserInfo user = UserInfo.newBuilder()
.name("pdc")
.userId(10000)
.email("[email protected]")
.mobile("155****8925")
.remark("remark info").build();
ctx.writeAndFlush(user);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
5.CustomV1Encoder
/**
* @author pdc
*/
public class CustomV1Encoder extends MessageToByteEncoder {
@Override
public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
//使用hessian序列化对象
byte[] data = HessianSerializer.serialize(in);
out.writeBytes(data);
}
}
6.CustomV1Decoder
/**
* @author pdc
*/
public class CustomV1Decoder extends ByteToMessageDecoder {
@Override
public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out){
//读取字节数组
int dataLength = in.readableBytes();
if (dataLength <= 0) return;
byte[] data = new byte[dataLength];
in.readBytes(data);
//将字节数组使用Hession反序列化为对象
Object obj = HessianSerializer.deserialize(data);
out.add(obj);
}
}
7.HessianSerializer
/**
* @author pdc
*/
public class HessianSerializer {
private HessianSerializer() {
}
public static byte[] serialize(Object obj) {
if (obj == null)
throw new NullPointerException();
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
HessianOutput ho = new HessianOutput(os);
ho.writeObject(obj);
return os.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public static <T> T deserialize(byte[] data) {
if (data == null) throw new NullPointerException();
try {
ByteArrayInputStream is = new ByteArrayInputStream(data);
HessianInput hi = new HessianInput(is);
return (T) hi.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
- 运行
先运行CustomServer的main,再运行CustomClient的main即可