1、Netty编解码器
- 数据在网络中是以二进制字节码的形式流动,而我们在端接收或发送的数据形式则各种各样(文本、图片、音视频等),因此需要在发送端对数据进行编码,在接收端对收到的数据解码;
- codeC(编解码器)的组成部分——Encoder(编码器)负责将业务数据转换为二进制字节码;Decoder(解码器)负责将二进制字节码转换为业务数据;
- Netty编码机制——StringEncoder / StringDecoder负责字符串数据对象的编解码;ObjectEncoder / ObjectDecoder负责java对象的编解码;
- Netty自带的ObjectEncoder和ObjectDecoder可以用于实现POJO对象或其他业务对象的编解码,其底层使用的仍是java的序列化技术,存在以下问题:
1)无法实现客户端与服务器端的跨语言; 2)序列化体积过大,是二进制字节码的5倍多; 3)序列化性能相对较低;
2、ProtoBuf概述
2-1 ProtoBuf简单介绍
- protoBuf是一种平台无关、语言无关的、可扩展且轻便高效的序列化数据结构的协议,适合用于数据存储和RPC(远程过程调用)数据交换格式;
- protoBuf是以Message的方式来管理数据的;
- 所谓“平台无关、语言无关”,即客户端和服务器可以使用不同的编程语言进行开发;
- protoBuf具有更高的性能和可靠性;
- 使用ProtoBuf编译器可以自动生成代码,ProtoBuf是把类的定义使用.proto文件描述出来,在通过proto.exe将.proto文件编译为.java文件:
2-2在IDEA中使用protoBuf的步骤(Linux环境下)
- 安装protoc插件
- 在maven项目中引入ProtoBuf的坐标,下载相关的jar包:
Pom.xml
<dependencies>
<!--https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
- 在IDEA中安装ProtoBuf Editor插件(用于编写.proto文件),安装ProtoBuf Generator插件(用于编译.proto文件,生成java文件)
- 编写.proto文件,并编译生成.java文件
3、ProtoBuf实例
3-1实例一
要求:
- 客户端可以发送一个Student PoJo对象到服务器(通过protobuf编码)
- 服务器能接收Student PoJo对象,并显示信息
实现:
Student PoJo对象:Student.proto
syntax = "proto3"; //版本
option java_outer_classname = "StudentPOJO"; //生成的外部类名称,同时文件名
//protobuf以message的形式管理数据
message Student{ //会在 studentPOJO 外部类生成一个内部类 Student,它是真正发送的POJO对象
int32 id = 1; //表示 Student 类中有一个属性 名字为id,类型为 int32(protoType),1表示属性的序号
string name = 2;
}
服务器端:NettyServer.java
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.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//在pipeline中加入ProtoBufferDecoder
//指定对哪一种对象进行解码
pipeline.addLast("decoder",new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture cf = serverBootstrap.bind(6668).sync();
//给 cf 添加监听器,监听我们感兴趣的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (cf.isSuccess()){
System.out.println("绑定端口 6668 成功");
}else {
System.out.println(cf.cause());
}
}
});
cf.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
自定义的服务器业务处理器:NettyServerHandler.java
package com.SF.Netty.codeC;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// @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
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端~",CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception {
System.out.println("*****客户端发送的数据 id = "+msg.getId() + " 名字 = "+msg.getName()+"*******");
}
}
客户端:NettyClient.java
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 io.netty.handler.codec.protobuf.ProtobufEncoder;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//在pipeline中加入ProtoBufferEncoder
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("encoder", new ProtobufEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
System.out.println("客户端已准备就绪~~~~");
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventExecutors.shutdownGracefully();
}
}
}
自定义的客户端业务处理器:NettyClientHandler.java
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 {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//发送一个 student 对象到服务器
StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(1001).setName("张三").build();
ctx.writeAndFlush(student);
}
@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();
}
}
3-2实例二
要求:
- 客户端可以随机发送Student PoJo / Worker PoJo对象到服务器
- 服务器能接收Student PoJo / Worker PoJo对象,判断是哪种类型,并显示信息
实现:
POJO对象:Student.proto
syntax = "proto3";
option optimize_for = SPEED; //快速解析
option java_package="com.SF.Netty.codeC2"; //指定生成到那个包下
option java_outer_classname = "MyDataInfo"; //指定外部类名称
//protoBuffer可以使用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;
string name = 2;
}
message Worker{
string name = 1;
int32 age = 2;
}
服务器端:NettyServer.java
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 {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//在pipeline中加入ProtoBufferDecoder
//指定对哪一种对象进行解码
pipeline.addLast("decoder",new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture cf = serverBootstrap.bind(6668).sync();
//给 cf 添加监听器,监听我们感兴趣的事件
cf.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (cf.isSuccess()){
System.out.println("绑定端口 6668 成功");
}else {
System.out.println(cf.cause());
}
}
});
cf.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
自定义的服务器业务处理器:NettyServerHandler.java
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// @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
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端~",CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@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("传输类型不正确");
}
}
}
客户端:NettyClient.java
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 io.netty.handler.codec.protobuf.ProtobufEncoder;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//在pipeline中加入ProtoBufferEncoder
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("encoder", new ProtobufEncoder());
pipeline.addLast(new NettyClientHandler());
}
});
System.out.println("客户端已准备就绪~~~~");
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventExecutors.shutdownGracefully();
}
}
}
![点击并拖拽以移动 wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==](https://i-blog.csdnimg.cn/blog_migrate/f6865ed330157e70d830d973b18aee83.png)
自定义的客户端业务处理器:NettyClientHandler.java
package com.SF.Netty.codeC2;
import com.SF.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 {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//随机发送一个Student 或 worker对象
int random = new Random().nextInt(3);
MyDataInfo.MyMessage myMessage = null;
if (random == 0){ //发送一个Student对象
myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType)
.setStudent(MyDataInfo.Student.newBuilder().setId(23).setName("李四")).build();
}else{
//发送一个worker对象
myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.WorkerType)
.setWorker(MyDataInfo.Worker.newBuilder().setAge(20).setName("王五")).build();
}
ctx.writeAndFlush(myMessage);
}
@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();
}
}