上一节学习了Netty的TCP拆包粘包问题的解决之道,今天学习Netty的序列化。
什么是序列化
引入百科:序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象
Java的序列化
Java原生API提供了对象的输入输出流ObjectIntputStream和ObjectOutputStream,可直接将Java对象作为可存储的字节数组写入到文件,也可以传输到网络上。
Java序列化的目的主要有两个:
1、网络传输
2、对象持久化
但是基于Java的序列化方式在效率和速度上都有明显缺陷,目前已出现多个序列化框架,我们先通过代码比较下Java序列化的缺点
@Data
public class UserInfo implements Serializable {
private static final long serialVersionUID = -3498249724990274743L;
private String userName;
private int userID;
public byte[] codeC() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
byte[] value = this.userName.getBytes();
buffer.putInt(value.length);
buffer.put(value);
buffer.putInt(this.userID);
buffer.flip();
value = null;
byte[] result = new byte[buffer.remaining()];
buffer.get(result);
return result;
}
}
测试程序
public class TestUserInfo {
public static void main(String[] args) {
UserInfo info = new UserInfo();
info.setUserID(100);
info.setUserName("wcs");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeObject(info);
os.flush();
os.close();
byte[] b = bos.toByteArray();
System.out.println("the jdk serializable length is:"+b.length);
bos.close();
System.out.println("-----------");
System.out.println("the byte array Serializable length is:"+info.codeC().length);
} catch (IOException e) {
e.printStackTrace();
}
}
}
由此可见采用JDK序列化编码后的二进制数组大小是二进制编码的10倍。
下面再比较下序列化的时间长短,对同一个对象进行100万次编码实验,然后统计耗费的总时间:
public class PerformsTestUserInfo {
public static void main(String[] args) throws IOException{
UserInfo info = new UserInfo();
info.setUserID(100);
info.setUserName("welcome to netty");
int loop = 100000;
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
long startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
bos = new ByteArrayOutputStream();
os = new ObjectOutputStream(bos);
os.writeObject(info);
os.flush();
os.close();
byte[] b = bos.toByteArray();
bos.close();
}
long endTime = System.currentTimeMillis();
System.out.println("---------jbk serializable cost time"+(endTime - startTime)+" ms");
System.out.println("-------");
ByteBuffer buffer = ByteBuffer.allocate(1024);
startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
byte[] b = info.codeC();
}
endTime = System.currentTimeMillis();
System.out.println("---------the byte array serializable cost time is"+(endTime - startTime)+" ms");
}
}
由此可见Java序列化的性能只有二进制编码的6.17%.
Netty Java序列化开发
使用Netty对POJO对象进行序列化开发,POJO对象如下:
@Data
public class SubscribeResp implements Serializable {
private static final long serialVersionUID = -4261173283103510587L;
private int subReqId;
private int respCode;
private String desc;
@Override
public String toString() {
return "subReqId="+subReqId+" respCode="+respCode+" desc="+desc;
}
}
在Netty服务端程序中添加解码器ObjectDecode和编码器ObjectEncode
服务端代码如下
public class SubReqServer {
public void bind(int port) throws Exception {
//创建两个线程组 一个用于服务端接收客户端的连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
//一个用于网络读写
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ObjectDecoder(1024 * 1024,
ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
socketChannel.pipeline().addLast(new ObjectEncoder());
socketChannel.pipeline().addLast(new SubReqServerHandler());
}
});
ChannelFuture future = b.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
try {
new SubReqServer().bind(port);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class SubReqServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
SubscribeResp resp = (SubscribeResp) msg;
if ("wcs".equals(resp.getDesc())) {
System.out.println("Service accept client subsribe resp:[" + resp.toString()+"]");
ctx.writeAndFlush(buildResponse(resp.getSubReqId()));
}
}
private SubscribeResp buildResponse(int subReqId) {
SubscribeResp resp = new SubscribeResp();
resp.setSubReqId(subReqId);
resp.setRespCode(0);
resp.setDesc("receive success");
return resp;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
在Netty客户端程序中添加解码器ObjectDecode和编码器ObjectEncoder
public class SubReqClient {
public void connect(int port,String host) throws Exception {
//创建读写io线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ObjectEncoder());
socketChannel.pipeline().addLast(new ObjectDecoder(1024 * 1024,
ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
socketChannel.pipeline().addLast(new SubReqClientHandler());
}
});
ChannelFuture f = b.connect(host,port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
int port = 8080;
if (args != null && args.length >0) {
try {
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e) {
}
}
try {
new SubReqClient().connect(port,"127.0.0.1");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class SubReqClientHandler extends ChannelHandlerAdapter {
public SubReqClientHandler() {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i< 10000; i++) {
ctx.write(subReq(i));
}
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Service accept client subsribe resp:[" + msg+"]");
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
private SubscribeResp subReq(int i) {
SubscribeResp subscribeResp = new SubscribeResp();
subscribeResp.setSubReqId(i);
subscribeResp.setDesc("wcs");
return subscribeResp;
}
}
服务端运行结果
客户端运行结果