打卡日期(2019-07-10)
学习要点
- 1.ProtobufVarint32FrameDecoder
- 2.ProtobufDecoder
- 3.ProtobufVarint32LenthFieldPrepender
1.ProtobufVarint32FrameDecoder
用于decode(解码)前解决半包和粘包问题(利用包头中包含数组长度来识别半包沾包)
什么是粘包
首先得了解一下TPC/IP的协议,在用户数据量非常小的情况下,极端情况下,一个字节,该TCP数据包的有效载荷非常低,传递100自己数据,需要循环100次TCP传送,100次ACK确认,在应用及时性不高的情况下,将这100个有效的数据拼接成一个数据包,这样就会压缩成一个TCP数据包,以及一个ack确认,有效载荷也提高了,带宽也省下来了。
非常极端的情况下,有可能两个数据包拼接成一个数据包,也有可能是一个半数据包拼接成一个数据包,也有可能是两个半数据包拼接成一个数据包。这个多个数据包拼接成一个数据包的过程就叫做粘包。
什么是拆包
拆包和粘包是相对的。一端粘了包另一端就需要去拆包。例如:客户端发送的3个数据包粘成了2个数据包,服务端接收到数据之后就需要把这2个粘好的包拆分成3个单独的数据包进行处理。
拆包的原理
数据包每次读取完都需要判断是否是一个完整的数据包
- 1.如果当前读取的数据不足以拼接成一个完整的业务数据包,那就保留改数据,继续从tcp缓冲区中获取数据值,直到得到一个完整的数据包
- 2.如果当前读到的数据加上已经读取的数据足够拼接成一个完整的数据包,那就将已经读到的数据拼接上次读取的数据,组成一个完整的数据包,多余的任然保留,以便和下次读取的数据进行拼接
2.ProtobufDecoder
ProtobufDecoder(MessageLite),这个解析器实际上就是告诉ProtobufDecoder要处理的目标类是什么,否则仅仅从字节数组中是无法判断出要解析的目标类型信息
3.ProtobufVarint32LengthFieldPrepender
netty 为protobuf提供的一个编码器
netty 利用protobuf单协议消息支持
《Netty学习打卡–从小白到放弃》----- 09 - netty 之protobuf 潜入protobuf 简单的案例
已经写好了Student.proto,并且利用protoc生成了Person类,接下来利用这个类来实现protobuf但协议消息支持
服务器端(Server)
package com.dragon.protobuf.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public class MyProtobufServer {
public static void main(String[] args) throws Exception{
EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup handlerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try{
serverBootstrap.group(acceptorGroup,handlerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new MyProtobufServerInit());
ChannelFuture future = serverBootstrap.bind(8080).sync();
future.channel().closeFuture().sync();
}finally {
acceptorGroup.shutdownGracefully();
handlerGroup.shutdownGracefully();
}
}
}
package com.dragon.protobuf.server;
import com.dragon.protobuf.Person;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
public class MyProtobufServerInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//用于decode(解码)前解决半包和粘包问题(利用包头中包含数组长度来识别半包沾包)
pipeline.addLast(new ProtobufVarint32FrameDecoder());
//这个解析器实际上就是告诉ProtobufDecoder要处理的目标类是什么,否则仅仅从字节数组中是无法判断出要解析的目标类型信息
pipeline.addLast(new ProtobufDecoder(Person.Student.getDefaultInstance()));
//protobuf编码器 对protobuf协议的的消息头上加上一个长度为32的整形字段,用于标志这个消息的长度
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
//protobuf编码器
pipeline.addLast(new ProtobufEncoder());
//自定义处理器
pipeline.addLast(new MyProtobufServerHandler());
}
}
package com.dragon.protobuf.server;
import com.dragon.protobuf.Person;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class MyProtobufServerHandler extends SimpleChannelInboundHandler<Person.Student> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Person.Student msg) throws Exception {
System.out.println(msg.getAge());
System.out.println(msg.getName());
System.out.println(msg.getAddress());
}
}
客户端(Client)
package com.dragon.protobuf.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class MyProtobufClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootstrap = new Bootstrap();
EventLoopGroup clientGroup = new NioEventLoopGroup();
try{
bootstrap.group(clientGroup).
channel(NioSocketChannel.class).
handler(new MyProtobufClientInit());
ChannelFuture channelFuture = bootstrap.connect("localhost",8080).sync();
channelFuture.channel().closeFuture().sync();
}finally {
clientGroup.shutdownGracefully();
}
}
}
package com.dragon.protobuf.client;
import com.dragon.protobuf.Person;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
public class MyProtobufClientInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//用于decode(解码)前解决半包和粘包问题(利用包头中包含数组长度来识别半包沾包)
pipeline.addLast(new ProtobufVarint32FrameDecoder());
//这个解析器实际上就是告诉ProtobufDecoder要处理的目标类是什么,否则仅仅从字节数组中是无法判断出要解析的目标类型信息
pipeline.addLast(new ProtobufDecoder(Person.Student.getDefaultInstance()));
//protobuf编码器 对protobuf协议的的消息头上加上一个长度为32的整形字段,用于标志这个消息的长度
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
//protobuf编码器
pipeline.addLast(new ProtobufEncoder());
//自定义处理器
pipeline.addLast(new MyProtobufClientHandler());
}
}
package com.dragon.protobuf.client;
import com.dragon.protobuf.Person;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class MyProtobufClientHandler extends SimpleChannelInboundHandler<Person.Student> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Person.Student msg) throws Exception {
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Person.Student student = Person.Student.newBuilder()
.setAge(100)
.setAddress("北京市西城区")
.setName("大江东去浪淘尽")
.build();
ctx.writeAndFlush(student);
}
}
分别运行Server和client,运行结果如下:
七月 10, 2019 3:53:42 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0xc3ba1456] REGISTERED
七月 10, 2019 3:53:42 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0xc3ba1456] BIND: 0.0.0.0/0.0.0.0:8080
七月 10, 2019 3:53:42 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0xc3ba1456, L:/0:0:0:0:0:0:0:0:8080] ACTIVE
七月 10, 2019 3:53:51 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xc3ba1456, L:/0:0:0:0:0:0:0:0:8080] READ: [id: 0xec4a0631, L:/127.0.0.1:8080 - R:/127.0.0.1:3099]
七月 10, 2019 3:53:51 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xc3ba1456, L:/0:0:0:0:0:0:0:0:8080] READ COMPLETE
100
大江东去浪淘尽
北京市西城区
已经成功运行,并且得到了相应的运行结果
但是现在有个问题,在Server端和Client的初始化容器中,分别有
pipeline.addLast(new ProtobufDecoder(Person.Student.getDefaultInstance()));
这样只支持单消息协议传递,那么如何实现多消息协议传递,下一章会进行解释
《Netty学习打卡–从小白到放弃》----- 11 - netty 之protobuf多消息协议传递