netty系列文章:
1 背景
在07-netty基础-自定义编解码器的基础上增加序列化和反序列化工厂,支持java、JSON形式的序列化和反序列化;核心思想就是想透过这种简单实现,了解其他框架的实现原理
2 依赖包
<dependencies>
<dependency>
<groupId>org.bonnie</groupId>
<artifactId>netty-message-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
</dependencies>
3 核心代码
3.1 序列化工厂设计
3.1.1 定义序列化和反序列化的接口
Serializer接口
package com.bonnie.netty.common.serializer;
import java.io.IOException;
public interface Serializer {
/**
* 反序列化:一个是要反序列化的对象类型,还一个是反序列化的字节数组
* @param clazz
* @param bytes
* @return
* @param <T>
* @throws IOException
*/
<T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException;
/**
* 序列化:需要的参数就是序列化的对象
* @param object
* @return
* @param <T>
* @throws IOException
*/
<T> byte[] serialize(T object) throws IOException;
}
3.1.2 JAVA实现
package com.bonnie.netty.common.serializer;
import java.io.*;
/**
* java序列化
*/
public class JavaSerializer implements Serializer {
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> byte[] serialize(T object) throws IOException {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
return bos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
3.1.3 JSON实现
package com.bonnie.netty.common.serializer;
import com.alibaba.fastjson.JSON;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class JsonSerializer implements Serializer {
@Override
public <T> T deserialize(Class<T> clazz, byte[] bytes) throws IOException {
String json = new String(bytes, StandardCharsets.UTF_8);
return (T) JSON.parseObject(json, clazz);
}
@Override
public <T> byte[] serialize(T object) throws IOException {
return JSON.toJSONString(object).getBytes(StandardCharsets.UTF_8);
}
}
3.1.4 简单工厂-按策略走对应的序列化方式(重点)
package com.bonnie.netty.common.serializer;
import com.bonnie.netty.common.enums.SerializableTypeEnum;
import java.io.IOException;
public class SerializerUtil {
public static <T> T deserializer(Class<T> clazz, byte[] bytes, byte serializerType) throws IOException {
SerializableTypeEnum serializableTypeEnum = SerializableTypeEnum.valueOf(serializerType);
switch (serializableTypeEnum) {
case JAVA:
return new JavaSerializer().deserialize(clazz, bytes);
case JSON:
return new JsonSerializer().deserialize(clazz, bytes);
default:
break;
}
return null;
}
public static <T> byte[] serializer(T object, byte serializerType) throws IOException {
SerializableTypeEnum serializableTypeEnum = SerializableTypeEnum.valueOf(serializerType);
switch (serializableTypeEnum) {
case JAVA:
return new JavaSerializer().serialize(object);
case JSON:
return new JsonSerializer().serialize(object);
default:
break;
}
return null;
}
}
3.2 序列化方式的传输方式
HeaderData中包了协议序列化方式(serializableType),在编码的时候可以通过msg.getHeader().getHeaderData().getSerializableType()方式轻松的拿到,然后进行相应方式的编解码;但是在解码的时候,需要先获取到序列化方式(serializableType),才能拿到,因为序列化方式(serializableType)是隐藏在HeaderData中,进而导致无法获取到serializableType。
解决方式一:headerData可以按照每个字段传输(固定形式,获取跟serializableType是没有关系的),这样获取的时候就不需要知道编解码
解决方式二:将serializableType放在headerLength的第一位即可
以下采用的是方式二:
3.2.1 headerLength的第一位是serializableType
/**
* @param headerLength:头的长度
* @param type:序列化方式
* @return
*/
public static byte[] markProtocolType(int headerLength,byte type){
byte[] result = new byte[4];
// 第一个8位放了序列化的方式
result[0] = type;
// 第二部分开始就放头的长度,把头的长度的第二个字节放好了
result[1] = (byte) ((headerLength >> 16) & 0xFF);
// 第三个字节放头的第三个字节
result[2] = (byte) ((headerLength >> 8) & 0xFF);
// 第四个字节放头的第四个字节
result[3] = (byte) (headerLength & 0xFF);
return result;
}
3.2.2 从headerLength中获取serializableType
private static byte getProtocolType(int oriHeaderLen) {
return(byte)((oriHeaderLen>>24) & 0xFF);
}
3.2.3 从headerLength中获取真实的长度
private static byte getProtocolType(int oriHeaderLen) {
return(byte)((oriHeaderLen>>24) & 0xFF);
}
4 重点代码
4.1 编码
package com.bonnie.netty.common.coder;
import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.serializer.SerializerUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
/**
* 编码器: 发送的信息构建成BonnieMessageRecord, 补充length以及headerLength
*/
public class BonnieEncoder extends MessageToByteEncoder<BonnieMessageRecord> {
@Override
protected void encode(ChannelHandlerContext ctx, BonnieMessageRecord msg, ByteBuf out) throws Exception {
// 在网络传输过程中,一定要对数据进行序列化
ByteBuffer header = encodeHead(msg);
out.writeBytes(header);
byte serializableType = msg.getHeader().getHeaderData().getSerializableType();
byte[] body = SerializerUtil.serializer(msg.getBody(), serializableType);
if(body!=null && body.length>0){
out.writeBytes(body);
}
}
private ByteBuffer encodeHead(BonnieMessageRecord msg) throws IOException {
// 整个报文长度字段的长度
int length = 4;
byte serializableType = msg.getHeader().getHeaderData().getSerializableType();
// 序列化头部信息
byte[] headerData =SerializerUtil.serializer(msg.getHeader(), serializableType);
// 加上头的长度
length += headerData.length;
// 序列化body
int bodyLength = SerializerUtil.serializer(msg.getBody(), serializableType).length;
length += bodyLength;
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + length - bodyLength);
// 整体的报文长度,也就是 headerLength+headerData + body
byteBuffer.putInt(length);
// 头的长度
// byteBuffer.putInt(headerData.length);
// 把序列化的方式藏在了头长度的第一个字节里面
byteBuffer.put(markProtocolType(headerData.length,serializableType));
// 头的数据
byteBuffer.put(headerData);
// 切换成读模式
byteBuffer.flip();
return byteBuffer;
}
public static <T> byte[] seriablizer(T object){
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* @param headerLength:头的长度
* @param type:序列化方式
* @return
*/
public static byte[] markProtocolType(int headerLength,byte type){
byte[] result = new byte[4];
// 第一个8位放了序列化的方式
result[0] = type;
// 第二部分开始就放头的长度,把头的长度的第二个字节放好了
result[1] = (byte) ((headerLength >> 16) & 0xFF);
// 第三个字节放头的第三个字节
result[2] = (byte) ((headerLength >> 8) & 0xFF);
// 第四个字节放头的第四个字节
result[3] = (byte) (headerLength & 0xFF);
return result;
}
}
4.2 解码
package com.bonnie.netty.common.coder;
import com.bonnie.netty.common.domain.User;
import com.bonnie.netty.common.entity.BonnieHeader;
import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.serializer.SerializerUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import lombok.extern.slf4j.Slf4j;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.ByteBuffer;
/**
* 解码:长度域解码器,将数据还原成对象BonnieMessageRecord的形式
*/
@Slf4j
public class BonnieDecoder extends LengthFieldBasedFrameDecoder {
public BonnieDecoder() {
super(Integer.MAX_VALUE, 0, 4, 0, 4);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf byteBuf = (ByteBuf) super.decode(ctx, in);
if (byteBuf == null) {
return null;
}
ByteBuffer byteBuffer = byteBuf.nioBuffer();
return decode(byteBuffer);
}
private static BonnieMessageRecord decode(ByteBuffer byteBuffer) throws IOException {
// 获取到byteBuf的长度
int length = byteBuffer.limit();
// 头和序列化方式组成的字节的长度
int oriHeaderLen = byteBuffer.getInt();
// 头真正的长度
int headerLength = getHeaderLength(oriHeaderLen);
// 头的字节数据
byte[] headerDataByte = new byte[headerLength];
byteBuffer.get(headerDataByte);
BonnieMessageRecord bonnieMessageRecord = new BonnieMessageRecord();
bonnieMessageRecord.setLength(length);
// 反序列化头数据得到头对象
BonnieHeader gpHeader = SerializerUtil.deserializer(BonnieHeader.class,headerDataByte,getProtocolType(oriHeaderLen));
gpHeader.setHeaderLength(headerLength);
bonnieMessageRecord.setHeader(gpHeader);
// 反序列化body对象
// 首先要知道body的长度
int bodyLength = length - 4 - headerLength;
byte[] bodyData = null;
if(bodyLength >0 ){
bodyData = new byte[bodyLength];
// 从byteBuffer中读取数据到bodyData里面
byteBuffer.get(bodyData);
}
bonnieMessageRecord.setBody(SerializerUtil.deserializer(User.class,bodyData,getProtocolType(oriHeaderLen)));
return bonnieMessageRecord;
}
private static int getHeaderLength(int oriHeaderLen) {
return oriHeaderLen & 0xFFFFFF;
}
private static byte getProtocolType(int oriHeaderLen) {
return(byte)((oriHeaderLen>>24) & 0xFF);
}
/**
* 反序列化
* @param clazz
* @param data
* @return
*/
private <T> T deserializable(Class<T> clazz, byte[] data) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new ByteArrayInputStream(data));
return (T)ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
4.3 服务端
package com.bonnie.server.netty;
import com.bonnie.netty.common.coder.BonnieDecoder;
import com.bonnie.netty.common.coder.BonnieEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class NettyServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline
.addLast(new BonnieEncoder())
.addLast(new BonnieDecoder())
.addLast(new ServerHandler());
}
});
try {
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
ServerHandler
package com.bonnie.server.netty;
import com.bonnie.netty.common.domain.User;
import com.bonnie.netty.common.entity.BonnieHeader;
import com.bonnie.netty.common.entity.BonnieHeaderData;
import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.enums.LanguageCodeEnum;
import com.bonnie.netty.common.enums.ReqTypeEnum;
import com.bonnie.netty.common.enums.SerializableTypeEnum;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BonnieMessageRecord record = (BonnieMessageRecord) msg;
System.out.println("收到客户端消息:"+record);
// 给客户端返回数据
BonnieMessageRecord messageRecord = new BonnieMessageRecord();
BonnieHeader header = new BonnieHeader();
BonnieHeaderData headerData = new BonnieHeaderData();
User user = (User) record.getBody();
Integer age = user.getAge();
headerData.setVersion(2);
headerData.setLanguageCode(LanguageCodeEnum.JAVA.getCode());
headerData.setSerializableType(SerializableTypeEnum.JAVA.getCode());
headerData.setReqType(ReqTypeEnum.RES.getCode());
header.setHeaderData(headerData);
messageRecord.setBody("客户端消息接收成功,用户的年龄是"+age);
ctx.writeAndFlush(record);
super.channelRead(ctx, msg);
}
}
4.4 客户端
package com.bonnie.client.netty;
import com.bonnie.netty.common.coder.BonnieDecoder;
import com.bonnie.netty.common.coder.BonnieEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
public class NettyClient {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline
.addLast(new BonnieEncoder())
.addLast(new BonnieDecoder())
.addLast(new ClientHandler());
}
});
try {
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
worker.shutdownGracefully();
}
}
}
package com.bonnie.client.netty;
import com.bonnie.netty.common.domain.User;
import com.bonnie.netty.common.entity.BonnieHeader;
import com.bonnie.netty.common.entity.BonnieHeaderData;
import com.bonnie.netty.common.entity.BonnieMessageRecord;
import com.bonnie.netty.common.enums.LanguageCodeEnum;
import com.bonnie.netty.common.enums.ReqTypeEnum;
import com.bonnie.netty.common.enums.SerializableTypeEnum;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i=0; i<10; i++) {
// 构建报文内容 length | headerLength | headerData | bodyData
BonnieMessageRecord messageRecord = new BonnieMessageRecord();
// 请求头信息
BonnieHeader header = new BonnieHeader();
BonnieHeaderData bonnieHeaderData = new BonnieHeaderData();
bonnieHeaderData.setVersion(0);
bonnieHeaderData.setLanguageCode(LanguageCodeEnum.JAVA.getCode());
bonnieHeaderData.setReqType(ReqTypeEnum.REQ.getCode());
bonnieHeaderData.setSerializableType(SerializableTypeEnum.JAVA.getCode());
// 请求内容
User user = new User();
user.setName("我是请求数据:"+i);
user.setAge(i);
// header.setHeaderLength();
header.setHeaderData(bonnieHeaderData);
// messageRecord.setLength();
messageRecord.setHeader(header);
messageRecord.setBody(user);
// System.out.println("客户端发送数据:"+messageRecord);
ctx.writeAndFlush(messageRecord);
}
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 将数据写入到服务端后,服务端回写数据到客户端,在这个方法中去接收数据
BonnieMessageRecord record = (BonnieMessageRecord) msg;
System.out.println("接收服务端消息:" + record);
super.channelRead(ctx, msg);
}
}
5 代码下载位置
地址: https://gitee.com/huyanqiu6666/netty.git
分支: 20250804-custom-message-serializer