Netty编解码技术 - Marshalling
编解码技术,说白了就是java序列化技术,序列化目的就两个:
- 第一进行网络传输,
- 第二对象持久化。
虽然我们可以使用java进行对象序列化,netty去传输,但是java序列化的硬伤太多,比如java序列化没法跨语言、序列化后码流太大、序列化性能太低等等..
主流的编解码框架:
- JBoss的Marshalling包 ( 适合 Java 与 Java 之间的传输)
- google的Protobuf
- 基于Protobuf的Kyro
- MessagePack框架
Marshalling适合 Java 与 Java 之间的传输,它的好处是可以直接把消息转成我们需要的对象.
在 Handler那块加上编写好的 编码,解码方法。
.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // 解码 ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder()); // 编码 ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder()); ch.pipeline().addLast(new ServerHandler()); } });
创建两个对象: Request , Response 用于请求数据和响应数据
Request
@Data
public class Request implements Serializable {
private static final long serialVersionUID = 8743187299189428700L;
private String id;
private String name;
private String requestMessage;
// 附件,比如图片...
private byte[] attachment;
Respone
@Data
public class Response implements Serializable {
private static final long serialVersionUID = -4639347903121316828L;
private String id;
private String name;
private String responseMessage;
编写
MarshallingCodeCFactory 编解码方法
public final class MarshallingCodeCFactory {
/**
* 创建Jboss Marshalling解码器MarshallingDecoder
* @return 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
* @return MarshallingEncoder
*/
public static MarshallingEncoder buildMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
// 构建Netty的MarshallingEncoder对象,MarshallingEncoder用于实现序列化接口的POJO对象序列化为二进制数组
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
然后在 server , client 使用 刚刚写的 MarshallingCodeCFactory 的编解码方法
server
public class Server {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_RCVBUF, 1024 * 32)
.childOption(ChannelOption.SO_SNDBUF, 1024 * 32)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 解码
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
// 编码
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(new ServerHandler());
}
});
//服务器端绑定端口并启动服务
ChannelFuture cf = sb.bind(8765).sync();
//使用channel级别的监听close端口 阻塞的方式
cf.channel().closeFuture().sync();
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
client.
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建两个线程组: 只需要一个线程组用于我们的实际处理(网络通信的读写)
EventLoopGroup workGroup = new NioEventLoopGroup();
//2 通过辅助类去构造server/client
Bootstrap b = new Bootstrap();
b.group(workGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.option(ChannelOption.SO_RCVBUF, 1024 * 32)
.option(ChannelOption.SO_SNDBUF, 1024 * 32)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
ch.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
ch.pipeline().addLast(new ClientHandler());
}
});
// 服务器端绑定端口并启动服务
ChannelFuture cf = b.connect("127.0.0.1", 8765).syncUninterruptibly();
// 写出实际的对象数据
for(int i =0; i < 10; i ++) {
Request req = create(i);
cf.channel().writeAndFlush(req);
}
cf.channel().closeFuture().sync();
workGroup.shutdownGracefully();
}
private static Request create(int seq) throws Exception {
Request request = new Request();
request.setId(seq + "");
request.setName("named:" + seq);
request.setRequestMessage("messaged:" + seq);
System.out.println("getProperty:"+System.getProperty("user.dir"));
String path = System.getProperty("user.dir") + File.separatorChar + "sources" + File.separatorChar + "001.jpg";
FileInputStream fis = new FileInputStream(new File(path));
byte[] data = new byte[fis.available()];
fis.read(data);
fis.close();
request.setAttachment(GzipUtils.gzip(data));
return request;
}
ServerHandler
package com.bfxy.netty.marshalling;
import java.io.File;
import java.io.FileOutputStream;
import com.bfxy.netty.utils.GzipUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.err.println("server channel active..");
}
/**
* 真正的数据最终会走到这个方法进行处理
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 读取客户端的数据
// 使用Marshalling的方便之处,可以直接转换成我们创建的对象。
Request req = (Request)msg;
System.err.println("Server: " + req.getId() + ", " + req.getName() + ", " + req.getRequestMessage());
// 假设传过来的数据是压缩的, 通过ungzip解压缩成一个 byte 数组
byte[] attachment = GzipUtils.ungzip(req.getAttachment());
System.out.println("dir : "+System.getProperty("user.dir") );
String path = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "001.jpg";
FileOutputStream fos = new FileOutputStream(path);
fos.write(attachment);
fos.close();
Response resp = new Response();
resp.setId(req.getId());
resp.setName("resp" + req.getName());
resp.setResponseMessage("响应内容: " + req.getRequestMessage());
ctx.writeAndFlush(resp);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
}
ClientHandler
package com.bfxy.netty.marshalling;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.err.println("client channel active..");
}
/**
* 真正的数据最终会走到这个方法进行处理
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf buf = (ByteBuf) msg;
byte[] request = new byte[buf.readableBytes()];
buf.readBytes(request);
String requestBody = new String(request, "utf-8");
System.err.println("Client: " + requestBody);
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.fireExceptionCaught(cause);
}
}