七、Google Protobuf
1、编码和解码的基本介绍
- 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送 数据时就需要编码,接收数据时就需要解码 [示意图]
- codec(编解码器) 的组成部分有两个:decoder(解码器)和 encoder(编码器)。 encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成 业务数据
2、Netty 本身的编码解码的机制和问题分析
-
Netty 自身提供了一些 codec(编解码器)
-
Netty 提供的编码器
• StringEncoder,对字符串数据进行编码
• ObjectEncoder,对 Java 对象进行编码
• …
-
Netty 提供的解码器
• StringDecoder, 对字符串数据进行解码
• ObjectDecoder,对 Java 对象进行解码
• …
-
Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现 POJO 对象或各种业务对象 的编码和解码,底层使用的仍是 Java 序列化技术 , 而Java 序列化技术本身效率就不高,存 在如下问题:
- 无法跨语言
- 序列化后的体积太大,是二进制编码的 5 倍多。
- 序列化性能太低
=>引出新的解决方案[Google的Protobuf]
3、Protobuf
Protobuf基本介绍和使用示意图
-
Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高 效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做 数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式 。 目前很多公司 http+json →tcp+protobuf
-
参考文档 : https://developers.google.com/protocol-buffers/docs/proto 语言指南 (翻墙)
-
Protobuf 是以 message 的方式来管理数据的.
-
支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的] (支持目前绝 大多数语言,例如 C++、C#、Java、python 等
-
高性能,高可靠性
-
使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用.proto 文件进行描 述。说明,在idea 中编写 .proto 文件时,会自动提示是否下载 .ptotot 编写插件. 可以让语法高亮。
-
然后通过 protoc.exe 编译器根据.proto 自动生成.java 文件
-
protobuf 使用示意图:
Protobuf快速入门实例
编写程序,使用Protobuf完成如下功能
- 客户端可以发送一个Student PoJo 对象到服 务器 (通过 Protobuf 编码)
- 服务端能接收Student PoJo 对象,并显示信 息(通过 Protobuf 解码)
- 具体 看演示步骤
引入依赖:
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
服务器端:
package com.lxg.netty.codec;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建BOSSGroup和WorkerGroup
/**
* 说明
* 1、创建了两个线程组boosGroup和workerGroup
* 2、boosGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
* 3、两个都是无限循环
* 4、bossGroup和workerGroup含有的子线程(NioEventLoop)的个数
* 默认是以cpu核数*2
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来进行设置
bootstrap.group(bossGroup, workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道来实现
.option(ChannelOption.SO_BACKLOG, 128) //设置线程队列等待连接的个数
.childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//.handler(null) //该handler对应boosGroup,childHandler对应workGroup
.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个初始化对象(匿名对象)
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//在pipeline加入ProtobufDecoder
//指定哪种对象进行解码
pipeline.addLast("Decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
ch.pipeline().addLast(new NettyServerHandler());
}
}); //给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println("服务器is ready......");
//绑定一个端口并且同步,生成了一个ChannelFuture对象
//启动服务器了(绑定好端口了)
ChannelFuture cf = bootstrap.bind(6668).sync();
//给cf注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if(cf.isSuccess()){
System.out.println("监听端口 6668成功");
}else{
System.out.println("监听端口 6668失败");
}
}
});
//对关闭通道进行监听
cf.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务器端handler:
package com.lxg.netty.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.CharsetUtil;
//继承入栈的handler适配器
/**
* 1、自定义一个Handler需要继续netty规定好的某个HandlerAdapter(规范)
* 2、这是我们自定义一个Handler,才能成为一个Handler
*/
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {
// *
// * 读取数据事件(这里我们可以读取客户端发送过来的消息)
// *
// * @param ctx :是上下文对象。含有管道pipeline,通道channel,地址
// * @param msg :就是客户端发送过来的数据,默认是Object
// * @throws Exception
//
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//
// //读取从客户端发送的StudentPOJO.Student
// StudentPOJO.Student student = (StudentPOJO.Student) msg;
// System.out.println("客户端发送的数据:id="+student.getId()+",名字="+student.getName());
// }
@Override
protected void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student student) throws Exception {
System.out.println("客户端发送的数据:id="+student.getId()+",名字="+student.getName());
}
/**
* 数据读取完毕
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush是write+flush
//将数据写入缓冲区,并刷新
//一般来讲,我们对这个发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端!(>^ω^<)喵",CharsetUtil.UTF_8));
}
/**
* 处理异常,一般是需要关闭通道
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端:
package com.lxg.netty.codec;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动对象
//注意客户端使用的不是ServerBootStrap而是Bootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//在pipeline中加入ProtoBufEncoder
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("encoder",new ProtobufEncoder());
pipeline.addLast(new NettyClientHandler()); //加入自己的处理器
}
});
System.out.println("客户端 is ok !");
//启动客户端去连接服务器端
//关于ChannelFuture后面再分析,涉及到netty的异步模型
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
客户端handler:
package com.lxg.netty.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当通道就绪就会触发该方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//发送一个Student对象到服务器
StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("李四").build();
//发送Teacher。Member......
ctx.writeAndFlush(student);
}
/**
* 当通道有读取事件时,会触发
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服务器回复的消息:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Student.proto:
syntax = "proto3";//版本
option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名
//protobuf是使用message管理数据
message Student{//会在StudentPOJO外部类生成一个内部类Student,他是真正发送的POJO对象
int32 id=1;//Student类中有一个属性 名字为id类型为int32(protobuf类型),1表示属性序号,不是值
string name = 2;
}
转化为StudentPOJO.java
Protobuf快速入门实例2
编写程序,使用Protobuf完成如下功能
- 客户端可以随机发送Student PoJo/ Worker PoJo 对 象到服务器 (通过 Protobuf 编码)
- 服务端能接收Student PoJo/ Worker PoJo 对象(需要 判断是哪种类型),并显示信息(通过 Protobuf 解码)
- 具体 看演示步骤
服务器端:
package com.lxg.netty.codec2;
import com.lxg.netty.codec.StudentPOJO;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建BOSSGroup和WorkerGroup
/**
* 说明
* 1、创建了两个线程组boosGroup和workerGroup
* 2、boosGroup只是处理连接请求,真正的和客户端业务处理,会交给workerGroup完成
* 3、两个都是无限循环
* 4、bossGroup和workerGroup含有的子线程(NioEventLoop)的个数
* 默认是以cpu核数*2
*/
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务器端的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来进行设置
bootstrap.group(bossGroup, workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道来实现
.option(ChannelOption.SO_BACKLOG, 128) //设置线程队列等待连接的个数
.childOption(ChannelOption.SO_KEEPALIVE, true) //设置保持活动连接状态
//.handler(null) //该handler对应boosGroup,childHandler对应workGroup
.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个初始化对象(匿名对象)
//给pipeline设置处理器
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//在pipeline加入ProtobufDecoder
//指定哪种对象进行解码
pipeline.addLast("Decoder",new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
ch.pipeline().addLast(new NettyServerHandler());
}
}); //给我们的workerGroup的EventLoop对应的管道设置处理器
System.out.println("服务器is ready......");
//绑定一个端口并且同步,生成了一个ChannelFuture对象
//启动服务器了(绑定好端口了)
ChannelFuture cf = bootstrap.bind(6668).sync();
//给cf注册监听器,监控我们关心的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if(cf.isSuccess()){
System.out.println("监听端口 6668成功");
}else{
System.out.println("监听端口 6668失败");
}
}
});
//对关闭通道进行监听
cf.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务器端handler:
package com.lxg.netty.codec2;
import com.lxg.netty.codec.StudentPOJO;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
//继承入栈的handler适配器
/**
* 1、自定义一个Handler需要继续netty规定好的某个HandlerAdapter(规范)
* 2、这是我们自定义一个Handler,才能成为一个Handler
*/
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {
// *
// * 读取数据事件(这里我们可以读取客户端发送过来的消息)
// *
// * @param ctx :是上下文对象。含有管道pipeline,通道channel,地址
// * @param msg :就是客户端发送过来的数据,默认是Object
// * @throws Exception
//
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//
// //读取从客户端发送的StudentPOJO.Student
// StudentPOJO.Student student = (StudentPOJO.Student) msg;
// System.out.println("客户端发送的数据:id="+student.getId()+",名字="+student.getName());
// }
@Override
protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
//根据datatype来显示不同的信息
MyDataInfo.MyMessage.DataType dataType = msg.getDataType();
if(dataType== MyDataInfo.MyMessage.DataType.StudentType){
MyDataInfo.Student student = msg.getStudent();
System.out.println("学生id为:"+student.getId()+",姓名为:"+student.getName());
}else if (dataType== MyDataInfo.MyMessage.DataType.workerType){
MyDataInfo.Worker worker = msg.getWorker();
System.out.println("工人年龄为:"+worker.getAge()+",姓名为:"+worker.getName());
}else{
System.out.println("传输的数据类型不正确,请检查!");
}
}
/**
* 数据读取完毕
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush是write+flush
//将数据写入缓冲区,并刷新
//一般来讲,我们对这个发送的数据进行编码
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端!(>^ω^<)喵",CharsetUtil.UTF_8));
}
/**
* 处理异常,一般是需要关闭通道
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端:
package com.lxg.netty.codec2;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
//客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建客户端启动对象
//注意客户端使用的不是ServerBootStrap而是Bootstrap
Bootstrap bootstrap = new Bootstrap();
//设置相关参数
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) //设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//在pipeline中加入ProtoBufEncoder
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("encoder",new ProtobufEncoder());
pipeline.addLast(new NettyClientHandler()); //加入自己的处理器
}
});
System.out.println("客户端 is ok !");
//启动客户端去连接服务器端
//关于ChannelFuture后面再分析,涉及到netty的异步模型
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
}
}
客户端handler:
package com.lxg.netty.codec2;
import com.lxg.netty.codec.StudentPOJO;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import java.util.Random;
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* 当通道就绪就会触发该方法
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//随机发送一个Student或Worker对象到服务器
int random = new Random().nextInt(3);
MyDataInfo.MyMessage myMessage = null;
if(0==random){//发送Student对象
myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType).setStudent(MyDataInfo.Student.newBuilder()
.setId(5).setName("王五").build()).build();
}else {//发送一个Worker对象
myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.workerType).setWorker(MyDataInfo.Worker.newBuilder()
.setAge(20).setName("小林").build()).build();
}
ctx.writeAndFlush(myMessage);
}
/**
* 当通道有读取事件时,会触发
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服务器回复的消息:"+buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
proto文件:
syntax = "proto3";
option optimize_for = SPEED;//加快解析
option java_package="com.lxg.netty.codec2";//指定生成到哪个包下
option java_outer_classname="MyDataInfo";//外部类名
//Protobuf可以使用message管理其他的message
message MyMessage{
//定义一个枚举类型
enum DataType{
StudentType=0;//在proto3要求enum的编号从0开始
workerType=1;
}
//用data_type来标识传的是哪个枚举类型
DataType data_type = 1;
//标识每次枚举类型最多只能出现其中的一个,节省空间
oneof dataBody{
Student student = 2;
Worker worker = 3;
}
}
message Student{
int32 id=1;//Student类的属性
string name=2;
}
message Worker{
string name=1;
int32 age=2;
}
八、Netty编解码器和handler的调用机制
1、基本说明
-
netty的组件设计:Netty的主要组件有Channel、EventLoop、ChannelFuture、 ChannelHandler、ChannelPipe等
-
ChannelHandler充当了处理入站和出站数据的应用程序逻辑的容器。例如,实 现ChannelInboundHandler接口(或ChannelInboundHandlerAdapter),你就 可以接收入站事件和数据,这些数据会被业务逻辑处理。当要给客户端发送响 应时,也可以从ChannelInboundHandler冲刷数据。业务逻辑通常写在一个或 者多个ChannelInboundHandler中。ChannelOutboundHandler原理一样,只不 过它是用来处理出站数据的
-
ChannelPipeline提供了ChannelHandler链的容器。以客户端应用程序为例,如 果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即 客户端发送给服务端的数据会通过pipeline中的一系列 ChannelOutboundHandler,并被这些Handler处理,反之则称为入站的
2、编码解码器
- 当Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会 被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会 被编码成字节。
- Netty提供一系列实用的编解码器,他们都实现了ChannelInboundHadnler或者 ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。 以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后, 它将调用由解码器所提供的decode()方法进行解码,并将已经解码的字节转发 给ChannelPipeline中的下一个ChannelInboundHandler。
3、解码器-ByteToMessageDecoder
-
关系继承图
-
由于不可能知道远程节点是否会 一次性发送一个完整的信息, tcp有可能出现粘包拆包的问题, 这个类会对入站数据进行缓冲, 直到它准备好被处理
-
一个关于ByteToMessageDecoder实例分析
public class ToIntegerDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (in.readableBytes() >= 4) { out.add(in.readInt()); } } } 说明: 1) 这个例子,每次入站从ByteBuf中读取4字节,将其解码为一个int,然后将它添加到下一个List中。 当没有更多元素可以被添加到该List中时,它的内容将会被发送给下一个ChannelInboundHandler。 int在被添加到List中时,会被自动装箱为Integer。在调用readInt()方法前必须验证所输入的ByteBuf是 否具有足够的数据 2) decode 执行分析图 [示意图]
4、Netty的handler链的调用机制
实例要求:
-
使用自定义的编码器和解码器来 说明Netty的handler 调用机制
客户端发送long -> 服务器 服务端发送long -> 客户端
-
案例演示
服务端:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public class MyServer { public static void main(String[] args) throws Exception { NioEventLoopGroup boosGroup = new NioEventLoopGroup(1); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(boosGroup,workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new MyServerInitializer());//自定义一个初始化类 ChannelFuture channelFuture = serverBootstrap.bind(7000).sync(); channelFuture.channel().closeFuture().sync(); }finally { boosGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
服务端Initializer:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; public class MyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); //入站的handler进行解码MyByteToLongDecoder pipeline.addLast(new MyByteToLongDecoder()); //出站的handler进行编码 pipeline.addLast(new MyLongToByteEncoder()); //加入自定义handler pipeline.addLast(new MyServerHandler()); } }
服务端handler:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; public class MyServerHandler extends SimpleChannelInboundHandler<Long> { @Override protected void channelRead0(ChannelHandlerContext ctx, Long msg) throws Exception { System.out.println("从客户端:"+ctx.channel().remoteAddress()+"读取到long"+msg); //给客户端发送一个long ctx.writeAndFlush(98765L); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
Decoder:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.util.concurrent.EventExecutorGroup; import java.util.List; public class MyByteToLongDecoder extends ByteToMessageDecoder { /** * decode会根据接收到的数据,被调用多次,直到确定没有新的元素被添加到list * 或者是ByteBuf没有更多的可读字节为止 * 如果list out不为空,就会将list的内容传递给下一个channelinboundhandler处理 * 该处理器的方法也会被调用多次 * * @param ctx:上下文对象 * @param byteBuf:入站的ByteBuf * @param list:list集合,将解码后的数据传给下一个handler * @throws Exception */ @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception { System.out.println("MyByteToLongDecoder中的decode被调用了!"); //因为long8个字节,需要判断有8个字节才能读取一个long if(byteBuf.readableBytes()>=8){ list.add(byteBuf.readLong()); } } }
Encoder:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; public class MyLongToByteEncoder extends MessageToByteEncoder<Long> { //编码方法 @Override protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf byteBuf) throws Exception { System.out.println("MyLongToByteEncoder中encode被调用了!"); System.out.println("msg="+msg); byteBuf.writeLong(msg); } }
客户端:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; public class MyClient { public static void main(String[] args) throws Exception { NioEventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(new MyClientInitializer());//自定义初始化类 ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 7000).sync(); channelFuture.channel().closeFuture().sync(); }finally { group.shutdownGracefully(); } } }
客户端Initializer:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; public class MyClientInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); //加入出站的handler对数据进行一个编码 pipeline.addLast(new MyLongToByteEncoder()); //这是入站的解码器(入站handler) pipeline.addLast(new MyByteToLongDecoder()); //加入自定义的handler,处理业务 pipeline.addLast(new MyClientHandler()); } }
客户端handler:
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.EventExecutorGroup; public class MyClientHandler extends SimpleChannelInboundHandler<Long> { @Override protected void channelRead0(ChannelHandlerContext channelHandlerContext, Long msg) throws Exception { System.out.println("服务器的ip="+channelHandlerContext.channel().remoteAddress()); System.out.println("收到服务器的消息是="+msg); } //重写channelActive发送数据 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("MyClientHandler发送数据"); // ctx.writeAndFlush(Unpooled.copiedBuffer("")); ctx.writeAndFlush(123456L);//发送的是一个long //分析 //1、"abcdabcdabcdacbd"是16个字节 //2、该处理器的前一个handler是MyLongToByteEncoder //3、MyLongToByteEncoder父类MessageToByteEncoder会判断当前msg是不是应该处理的类型 //如果是就处理,不是就跳过 //4、因此我们在编写Encoder时要注意传入的数据类型和处理的数据类型一致 // ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdasdfasdf", CharsetUtil.UTF_8)); } }
-
结论:
- 不论解码器handler 还是 编码器handler 即接 收的消息类型必须与待处理的消息类型一致, 否则该handler不会被执行
- 在解码器 进行数据解码时,需要判断 缓存 区(ByteBuf)的数据是否足够 ,否则接收到的结果和期望结果可能不一致
5、解码器-ReplayingDecoder
-
public abstract class ReplayingDecoder extends ByteToMessageDecoder
-
ReplayingDecoder扩展了ByteToMessageDecoder类,使用这个类,我们不必调用 readableBytes()方法。参数S指定了用户状态管理的类型,其中Void代表不需要状态 管理
-
应用实例:使用ReplayingDecoder 编写解码器,对前面的案例进行简化 [案例演示]
-
ReplayingDecoder使用方便,但它也有一些局限性:
- 并不是所有的 ByteBuf 操作都被支持,如果调用了一个不被支持的方法,将会抛出一个 UnsupportedOperationException。
- ReplayingDecoder 在某些情况下可能稍慢于 ByteToMessageDecoder,例如网络缓慢并且消息格 式复杂时,消息会被拆成了多个碎片,速度变慢
package com.lxg.netty.inboundhandlerandoutboundhandler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ReplayingDecoder; import java.util.List; public class MyByteToLongDecoder2 extends ReplayingDecoder<Void> { @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { System.out.println("MyByteToLongDecoder2中的decode被调用了!"); //在ReplayingDecoder中不需要判断数据是否足够读取,内部会进行处理判断 list.add(byteBuf.readLong()); } }
6、其它编解码器
其它解码器
- LineBasedFrameDecoder:这个类在Netty内 部也有使用,它使用行尾控制字符(\n或者\r\n) 作为分隔符来解析数据。
- DelimiterBasedFrameDecoder:使用自定义 的特殊字符作为消息的分隔符。
- HttpObjectDecoder:一个HTTP数据的解码器
- LengthFieldBasedFrameDecoder:通过指定 长度来标识整包消息,这样就可以自动的处理 黏包和半包消息。
7、Log4j 整合到Netty
-
在Maven 中添加对Log4j的依赖 在 pom.xml
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.25</version> <scope>test</scope> </dependency>
-
配置 Log4j , 在 resources/log4j.propertie
log4j.rootLogger=DEBUG,stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%p] %C{1} - %m%n
-
演示整合