java架构之路-(netty专题)netty的编解码(出入战)与粘包拆包
上次回归:
上次博客我们主要说了netty的基本使用,都是一些固定的模式去写的,我们只需要关注我们的拦截器怎么去写就可以了,然后我们用我们的基础示例,改造了一个简单的聊天室程序,可以看到内部加了一个StringEncoder和StringDecoder,这个就是用来编解码我们字符串的,这次我们就来说说这个编解码。
编码&解码:
上次我们写的那个简单的聊天室程序大家还记得吧,内部加了两个类似拦截器的玩意。
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new StringDecoder());
一个是编码的,一个是解码的,也是借着这个东西,来个大家说一下我们的ChannelPipline,上次只是简单了说了一下我们的ChannelPipline内部存放了ChannelHandler,而且是双向链表的结构来存储的,我们这次来细化一下我们这个拦截器是怎么工作的。
内部大概是这个样子的,每次放入的时候有顺序的放置,暂时先说有顺序,后面我会详细解释这个什么时候顺序生效,记住是由由头开始放置,这里涉及到两个概念就是入站和出站。
就是说我们从客户端发送数据到服务端,叫做出站,会经过一系列的ChannelOutboundHandler,可以方便记忆为一系列的出站拦截器,我们想出站,就要经过出站拦截器。
反之我们的入站就是和出站相对应的,是由服务端发送过来的数据,经由我们的一系列ChannelInboundHandler,到达我们的客户端,其实出站入站你站在客户端的角度来看就很好理解了,我们客户端想发出去数据,就是出站,想进来数据(接收数据),就是入站,出站会经过out拦截器,入站会经过in拦截器。切记,是双向的,客户端和服务端不是共有一个ChannelPipline,而且这个出站和入站都是相对的,可能还是有一点抽象,我们来拿着我们聊天室的例子来看一下。
我们需要先明确我们的StringEncoder和StringDecoder是ChannelInboundHandler还是ChannelOutboundHandler。
StringEncoder是ChannelOutboundHandler,StringDecoder是ChannelInboundHandler,这回我们按照我们的代码画一下图。先弄一个客户端发送消息的。
简单解释一下,我们的服务端和客户端都有自己的ChannelPipline,我们的客户端要发送消息,相当于客户端是出站操作,我们要发送,数据外流,显然是数据要出去,出站操作啊, 出站要经过Encoder然后是我们自己的Clienthandler,走你,进入网络传输,对于我们的服务端来说,要接收数据,数据要进来,一定是入站操作啊,经过我们的Decoder,然后经过我们自己的Serverhandler,到达我们的服务端。
客户端往外发送消息,客户端是出站操作,经过Encoder,然后经过我们ServerHandler,进入网络,我们客户端是入站操作,经过Decoder,经过我们的ClientHandler,到达我们的服务端。这样说应该就理解了吧,你可以自己在decode和encode方法上打断点,自己调试一下,后面我会说源码,自己也是先熟悉一下源码。出站一定是从尾到头,入站一定是从头到尾,别问我为什么,我自己写了测试类,测试一下午了.....
我们可以看到Handler是按照顺序执行的,这个顺序只是对于相同类型的Handlery有效果的,像我们的Decoder和Encoder,一个是入站的Handler,一个是出站的Handler,他俩谁在前,谁在后无所谓的。
粘包&拆包:
粘包和拆包比上面那个出入战好理解很多很多,我们先来看一段代码。
package com.xiaocai.packing; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
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;
import io.netty.util.CharsetUtil; public class NettyClient {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();// 开启工作线程组
try {
Bootstrap bootstrap = new Bootstrap(); //创建一个和服务端相对应的server
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) //使用NioSocketChannel作为客户端的通道实现
.handler(new ChannelInitializer<SocketChannel>() {//设置回调函数
@Override
protected void initChannel(SocketChannel ch) { }
}); ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync();//启动客户端去连接服务器端
//对通道关闭进行监听
System.out.println("netty client start。。准备开始发送数据");
for (int i = 0; i < 2000; i++) {
ByteBuf buf = Unpooled.copiedBuffer("hello,xiaocaiJAVA!".getBytes(CharsetUtil.UTF_8));
cf.channel().writeAndFlush(buf);
}
System.out.println("发送数据完毕");
cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();//关闭线程组
}
}
}
就是什么意思呢?我们建立一个客户端连接,然后我们多次向我们的服务端发送消息,理论上我们每次收到的消息都应该是hello,xiaocaiJAVA!,我们来看一下结论。
我们可以看到,有部分是正常的,有一部分是hello,xiaocaiJAVA!hello,xiaocaiJAVA!有的还是o,什么什么,这个明显错误的,也就是我们的粘包拆包,为什么会出现这个呢?netty收到我们的消息不是马上发送出去,大概会等待一个瞬间,然后再发送我们的消息,在等待的瞬间再次进来的消息,他会一次性的发送出去,但是netty自身并不知道我们的消息该从何位置截断,所以就出现了我们看到的粘包拆包问题,我们来看一下解决方法。
我们每次可以把数据发过去,而且把数据的长度带过去就OK了,然后客户端每次优先判断一下数据的长度就可以了,看一下我这的解决方案。
package com.xiaocai.packing; /**
* 自定义协议包
*/
public class MyMessageProtocol { //定义一次发送包体长度
private int len;
//一次发送包体内容
private byte[] content; public int getLen() {
return len;
} public void setLen(int len) {
this.len = len;
} public byte[] getContent() {
return content;
} public void setContent(byte[] content) {
this.content = content;
}
}
package com.xiaocai.packing; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; public class MyMessageEncoder extends MessageToByteEncoder<MyMessageProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, MyMessageProtocol msg, ByteBuf out) throws Exception {
out.writeInt(msg.getLen());
out.writeBytes(msg.getContent());
}
}
package com.xiaocai.packing; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; public class MyMessageDecoder extends ByteToMessageDecoder { int length = 0; @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if(in.readableBytes() >= 4) {
if (length == 0){
length = in.readInt();
}
if (in.readableBytes() < length) {
System.out.println("当前可读数据不够,继续等待。。");
return;
}
byte[] content = new byte[length];
if (in.readableBytes() >= length){
in.readBytes(content); //封装成MyMessageProtocol对象,传递到下一个handler业务处理
MyMessageProtocol messageProtocol = new MyMessageProtocol();
messageProtocol.setLen(length);
messageProtocol.setContent(content);
out.add(messageProtocol);
}
length = 0;
}
}
}
package com.xiaocai.packing; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil; public class PackingServerHandler extends SimpleChannelInboundHandler<MyMessageProtocol> { private int count; @Override
protected void channelRead0(ChannelHandlerContext ctx, MyMessageProtocol msg) throws Exception {
System.out.println("====服务端接收到消息如下====");
System.out.println("长度=" + msg.getLen());
System.out.println("内容=" + new String(msg.getContent(), CharsetUtil.UTF_8));
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
package com.xiaocai.packing; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
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;
import io.netty.util.CharsetUtil; public class PackingClient {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MyMessageEncoder());
}
}); ChannelFuture cf = bootstrap.connect("127.0.0.1", 9000).sync(); for(int i = 0; i< 200; i++) {
String msg = "你好,小菜JAVA!";
//创建协议包对象
MyMessageProtocol messageProtocol = new MyMessageProtocol();
messageProtocol.setLen(msg.getBytes(CharsetUtil.UTF_8).length);
messageProtocol.setContent(msg.getBytes(CharsetUtil.UTF_8)); cf.channel().writeAndFlush(messageProtocol);
} cf.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
简单来说就是我们封装一个协议包,包含我们的内容和长度,我们要客户端传消息之前,信息进行处理,给予长度,然后我客户端接收数据时优先判断长度,长度不够继续等待,这样就解决了我们的拆包粘包问题,有的人还会提出用什么特殊符号的方法,也是可行的,但是你的数据中一定不要包含那个特殊符号,而且每次来一个新的开发人员,都要了解你们的特殊符号协议,还是比较麻烦的。
总结:
这次我们主要说了ChannelPipline内部的结构和addList时的放置顺序,netty的入战出战,是相对的,出站走out拦截器,入站走in拦截器,入站一定是从头到尾的,出站一定是从尾到头的,切记~!!!
最进弄了一个公众号,小菜技术,欢迎大家的加入
java架构之路-(netty专题)netty的编解码(出入战)与粘包拆包的更多相关文章
- java架构之路-(netty专题)netty的基本使用和netty聊天室
上次回顾: 上次博客,我们主要说了我们的IO模型,BIO同步阻塞,NIO同步非阻塞,AIO基于NIO二次封装的异步非阻塞,最重要的就是我们的NIO,脑海中应该有NIO的模型图. Netty概念: Ne ...
- [转帖]java架构之路-(面试篇)JVM虚拟机面试大全
java架构之路-(面试篇)JVM虚拟机面试大全 https://www.cnblogs.com/cxiaocai/p/11634918.html 下文连接比较多啊,都是我过整理的博客,很多答案都 ...
- TCP粘包/拆包 ByteBuf和channel 如果没有Netty? 传统的多线程服务器,这个也是Apache处理请求的模式
通俗地讲,Netty 能做什么? - 知乎 https://www.zhihu.com/question/24322387 谢邀.netty是一套在java NIO的基础上封装的便于用户开发网络应用程 ...
- Netty(三)TCP粘包拆包处理
tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...
- Netty(二)——TCP粘包/拆包
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...
- Netty 粘包 & 拆包 & 编码 & 解码 & 序列化 介绍
目录: 粘包 & 拆包及解决方案 ByteToMessageDecoder 基于长度编解码器 基于分割符的编解码器 google 的 Protobuf 序列化介绍 其他的 前言 Netty 作 ...
- 【转】Netty之解决TCP粘包拆包(自定义协议)
1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...
- Netty之解决TCP粘包拆包(自定义协议)
1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...
- Netty使用LineBasedFrameDecoder解决TCP粘包/拆包
TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...
随机推荐
- AcWing 247. 亚特兰蒂斯 | 扫描线
传送门 题目描述 有几个古希腊书籍中包含了对传说中的亚特兰蒂斯岛的描述. 其中一些甚至包括岛屿部分地图. 但不幸的是,这些地图描述了亚特兰蒂斯的不同区域. 您的朋友Bill必须知道地图的总面积. 你自 ...
- 关于Itext 报错-java.lang.NoClassDefFoundError: org/bouncycastle/asn1/ASN1Encodable
如果我们在用iText 做为java 为PDF 文档加水印的时候 报如下异常 java.lang.NoClassDefFoundError: org/bouncycastle/asn1/ASN1Enc ...
- 【Java基础总结】IO流
字节流 1. InputStream 字节输入流 代码演示 InputStream in = System.in; System.out.println("int read(byte b) ...
- 【php学习】图片处理三步走
前两天要对一张图片进行处理,其实很简单,就是在图片上加上字符串,一个图片而已,但是自己如同得了短暂性失忆似的,图片操作的函数一个都想不起来.所以就抽空整理了一下图片操作函数. 1. 创建画布 从文件中 ...
- 【转】提升你的Java应用性能:改善数据处理
提升你的Java应用性能:改善数据处理 作者:贾小骏 发布于07月26日 10:17 许多应用程序在压力测试阶段或在生产环境中都会遇到性能问题.如果我们看一下性能问题背后的原因,会发现很多是由数据处 ...
- Microsoft Azure Storage Explorer(2)
之前写过一个往Microsoft Azure Storage Explorer里存储的功能,现在又要把东西给下载下来. 记录一下: public string DownFileFromAzure() ...
- idea 2019.3 最新版破解教程
背景 最近,idea又被整治了,所以一大批激活码都失效了.我之前已经有2018版的永久激活了,所以非常淡定~,也没打算升级版本.但是,最近发现周围的人都在讨论这个问题.于是,我也找到了2019.3最新 ...
- ORM基础2 字段及其参数和meta
一.ORM简介 1.概念:ORM(Object Relational Mappingt ),对象关系映射 2.实质:类与数据库之间的映射 3.优点: 开发人员不用写数据库 4.缺点: 开发人员,数据库 ...
- 1.异常(Error和Exception)
什么是异常 比如上午我们一般是开车去上班,正常情况下,一般都不会迟到,但是今天突然有个问题,车坏了或者限行了,于是乎你改坐公交了,就有可能会迟到,这就属于一种异常的情况.在实际生活中呢,可能会遇到很多 ...
- Java入门 - 高级教程 - 02.集合
原文地址:http://www.work100.net/training/java-collection.html 更多教程:光束云 - 免费课程 集合 序号 文内章节 视频 1 概述 2 集合接口 ...