Netty编解码技术
编解码技术,说白了就是java序列化技术,序列化目的就两个。第一进行网络传输,第二进行对象持久化。
虽然我们可以使用java进行对象序列化,netty去传输,但是java序列化的硬伤太多,比如java序列化没法跨语言、序列化后码流太大、序列化性能太低等等。
主流的编解码框架:
JBoss的Marshalling包
Google的Protobuf
基于Protobuf的Kyro
MessagePack框架
JBoss Marshalling
JBoss Marshalling是一个java对象序列化包,对JDK默认的序列化框架进行了优化,但又保持跟java.io.Serializable接口的兼容,同时增加了一些可可调的参数和附加特性。
类库:jboss-marshalling-1.3.0、jboss-marshalling-serial-1.3.0
下载地址:https://www.jboss.org/jbossmarshalling/downloads
JBoss Marshalling与Netty结合后进行序列化对象的代码编写非常简单。
使用Marshalling来对java对象序列化编码和解码,这样可以直接传输数据对象,且再获得数据的时候直接强转为对应的类性即可。
/**
* 客户端需要发送的包装类
*/
package edu.sdut.marshalling;
import java.io.Serializable;
import java.util.Arrays;
public class Req implements Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String name;
private String requestMessage;
private byte[] attachment;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRequestMessage() {
return requestMessage;
}
public void setRequestMessage(String requestMessage) {
this.requestMessage = requestMessage;
}
public byte[] getAttachment() {
return attachment;
}
public void setAttachment(byte[] attachment) {
this.attachment = attachment;
}
@Override
public String toString() {
return "Req [id=" + id + ", name=" + name + ", requestMessage=" + requestMessage + ", attachment="
+ Arrays.toString(attachment) + "]";
}
}
/**
* 服务端需要回应的包装类
*/
package edu.sdut.marshalling;
import java.io.Serializable;
public class Resp implements Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String name;
private String responseMessage;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
@Override
public String toString() {
return "Resp [id=" + id + ", name=" + name + ", responseMessage=" + responseMessage + "]";
}
}
/**
* 创建
* Marshalling编码器和解码器
*/
package edu.sdut.marshalling;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.Marshalling;
import org.jboss.marshalling.MarshallingConfiguration;
import io.netty.handler.codec.marshalling.DefaultMarshallerProvider;
import io.netty.handler.codec.marshalling.DefaultUnmarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallerProvider;
import io.netty.handler.codec.marshalling.MarshallingDecoder;
import io.netty.handler.codec.marshalling.MarshallingEncoder;
import io.netty.handler.codec.marshalling.UnmarshallerProvider;
public class MarshallingCodeCFactory {
/**
* 创建Jboss Marshalling解码器MarshallingDecoder
*/
public static MarshallingDecoder buildMarshallingDecoder() {
//首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//创建了MarshallingConfiguration对象 配置了版本号为5
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//根据marshallerFactory和configuration创建provider
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory,configuration);
//构建Netty的MarshallingDecoder对象,两个参数分别为provider和单个序列化后的最大长度
MarshallingDecoder decoder = new MarshallingDecoder(provider,1024*1024*1);
return decoder;
}
/**
*创建JBoss Marshalling编码器MarshallingEncoder
*/
public static MarshallingEncoder buildMarshallingEncoder() {
//首先通过Marshalling工具类的精通方法获取Marshalling实例对象 参数serial标识创建的是java序列化工厂对象
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//创建了MarshallingConfiguration对象 配置了版本号为5
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//根据marshallerFactory和configuration创建provider
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory,configuration);
//创建Netty的MarshallingEncoder对象 MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
/**
* 服务端处理类
*/
package edu.sdut.marshalling;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Scanner;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/**
* 因为使用了Marshalling解码器,所以这里可以直接对传输过来的数据进行强转类型
* 而不再使用ByteBuf转换
*/
Req req = (Req)msg;
System.out.println("Server : " + req.getId() + "," + req.getName() + "," + req.getRequestMessage());
/****
* 对传过来的byte数据进行数据数据转换,写到文件中
* System.getProperty("user.dir")为特定写法,表示当前项目的目录
* File.separator表示文件之间的分隔符:windows中是"/",Linux中是"\";
*/
byte[] data = req.getAttachment();
String path = System.getProperty("user.dir") + File.separator + "image2" + File.separator + "001.jpg";
File file = new File(path);
FileOutputStream out = new FileOutputStream(file);
out.write(data);
out.close();
/**
* Server端得到数据后在返回数据给Client端
*/
Resp resp = new Resp();
resp.setId(req.getId());
resp.setName("resp" + req.getId());
resp.setResponseMessage("响应内容 " + req.getId());
ctx.writeAndFlush(resp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
/**
* 客户端处理类
*/
package edu.sdut.marshalling;
import java.util.Scanner;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/**
* 注释见ServerHandler
*/
Resp resp = (Resp)msg;
System.out.println("Client : " + resp.getId() + "," + resp.getName() + "," + resp.getResponseMessage());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
/***
* 服务端netty实现与配置
*/
package edu.sdut.marshalling;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class Server {
public static void main(String[] args) throws Exception {
//1、第一个线程组是用于接收Client端的接收的
EventLoopGroup bossGroup = new NioEventLoopGroup();
//2、第二个线程组是用于实际的的业务处理操作的
EventLoopGroup workerGroup = new NioEventLoopGroup();
//3、创建一个辅助类,对Server端进行一系列的配置
ServerBootstrap b = new ServerBootstrap();
//将两个线程组加入进来
b.group(bossGroup,workerGroup)
//执行使用NioServerSocketChannel类型的通道
.channel(NioServerSocketChannel.class)
//在这添加一个handler作为打印日志使用
.handler(new LoggingHandler(LogLevel.INFO))
//一定要使用childHandler去保定具体的事件处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
/**
* 重点
* 对数据对象进行编码解码
*/
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
//ServerHandler数据处理类
sc.pipeline().addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG,128)
.option(ChannelOption.SO_SNDBUF, 32*1024) //设置发送缓冲大小
.option(ChannelOption.SO_RCVBUF, 32*1024) //设置接收缓冲大小
//保持连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定指定的端口进行监听,可以绑定多个端口
ChannelFuture cf = b.bind(8765).sync(); //异步的方法
//相当于一个阻塞的一个作用,不能让主程序停止,长连接
cf.channel().closeFuture().sync();
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
/**
* 客户端netty实现and配置
*/
package edu.sdut.marshalling;
import java.io.File;
import java.io.FileInputStream;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Client端
* @author Vision_TXG
*
*/
public class Client {
public static void main(String[] args) throws Exception {
//建立线程组
EventLoopGroup group = new NioEventLoopGroup();
//创建一个辅助类,对Server端进行一系列的配置
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
/**
* 重点
* 配置 使用Marshalling对数据对象进行解码编码
*/
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
//ClientHandler处理类
sc.pipeline().addLast(new ClientHandler());
}
});
//非阻塞的异步通道,与Server端建立连接
ChannelFuture cf = b.connect("127.0.0.1",8765).sync();
//发送数据对象,在发送之前已经对对象编码序列化
for(int i = 0;i<5;i++) {
Req req = new Req();
req.setId(""+i);
req.setName("pro"+i);
req.setRequestMessage("数据信息"+i);
//***这里是对一个图片转换为byte类型进行传输
String path = System.getProperty("user.dir") + File.separator + "image1" + File.separator + "loginbk.jpg";
File file = new File(path);
FileInputStream in = new FileInputStream(file);
byte[] data = new byte[in.available()];
in.read(data);
in.close();
//在传送之前可以先压缩数据
req.setAttachment(data);
//***
//将数据对象写入通道并刷新
cf.channel().writeAndFlush(req);
}
//发送完数据后保持与服务器的连接,长连接
cf.channel().closeFuture().sync();
group.shutdownGracefully();
}
}