上次回归:

  上次博客我们主要说了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的编解码(出入战)与粘包拆包的更多相关文章

  1. java架构之路-(netty专题)netty的基本使用和netty聊天室

    上次回顾: 上次博客,我们主要说了我们的IO模型,BIO同步阻塞,NIO同步非阻塞,AIO基于NIO二次封装的异步非阻塞,最重要的就是我们的NIO,脑海中应该有NIO的模型图. Netty概念: Ne ...

  2. [转帖]java架构之路-(面试篇)JVM虚拟机面试大全

    java架构之路-(面试篇)JVM虚拟机面试大全 https://www.cnblogs.com/cxiaocai/p/11634918.html   下文连接比较多啊,都是我过整理的博客,很多答案都 ...

  3. TCP粘包/拆包 ByteBuf和channel 如果没有Netty? 传统的多线程服务器,这个也是Apache处理请求的模式

    通俗地讲,Netty 能做什么? - 知乎 https://www.zhihu.com/question/24322387 谢邀.netty是一套在java NIO的基础上封装的便于用户开发网络应用程 ...

  4. Netty(三)TCP粘包拆包处理

    tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...

  5. Netty(二)——TCP粘包/拆包

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...

  6. Netty 粘包 & 拆包 & 编码 & 解码 & 序列化 介绍

    目录: 粘包 & 拆包及解决方案 ByteToMessageDecoder 基于长度编解码器 基于分割符的编解码器 google 的 Protobuf 序列化介绍 其他的 前言 Netty 作 ...

  7. 【转】Netty之解决TCP粘包拆包(自定义协议)

    1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...

  8. Netty之解决TCP粘包拆包(自定义协议)

    1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...

  9. Netty使用LineBasedFrameDecoder解决TCP粘包/拆包

    TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...

随机推荐

  1. ENS使用指南系列之一 [ 注册 .eth 域名详细教程 ]

    ENS 域名系统中目前支持三种顶级域名,分别是 .eth .xyz .luxe.其中, .eth 是 ENS 系统的原生域名,是由一系列智能合约控制的去中心化的域名,另外两种是从互联网域名中接入的,要 ...

  2. 侠说java8--Stream流操作学习笔记,都在这里了

    前言 首次接触到Stream的时候以为它是和InputStream.OutputStream这样的输入输出流的统称. 流和集合的前世今生 概念的差异 在开发中,我们使用最多的类库之一就是集合.集合是一 ...

  3. Docker入门之快速安装和卸载使用Centos7

    一.检查内核版本 注意:Docker要求操作系统必须是64位,如果使用的Centos内核版本为3.10以上 执行命令:uname  -r 二.安装依赖软件包 执行命令:yum install -y y ...

  4. async-await 线程分析

    这里没有线程 原文地址:https://blog.stephencleary.com/2013/11/there-is-no-thread.html 前言 我是在看 C#8.0 新特性异步流时在评论里 ...

  5. mysql复习2

    -- 1. 创建和管理表 CREATE TABLE -- 方式一:CREATE TABLE emp1( id INT(10), `name` VARCHAR(20), salary DOUBLE(10 ...

  6. javaWeb传收参数方式总结

    有时候,我真会被传参搞得头晕,这样传要怎么接收,那样传又要怎么接收? get可以json吗?什么是json方式提交?等等问题,已困扰我许久 所以,在此想做个总结,整理一下思绪,不再为传收参烦恼!如有错 ...

  7. 2、pycharm安装及相关配置

    PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试. 语法高亮.Project管理.代码跳转.智能提示.自动完成.单元测试.版本控制 ...

  8. 基于BIO的实时Socket读写操作

    文章目录 前言 Socket类2.1 Socket的通信过程2.2 控制Socket连接2.3 设置Socket的选项 ServerSocket类3.1 构造ServerSocket3.2 Serve ...

  9. HTML中CSS引用——选择器的使用

    一.元素选择器     1.书写格式:标记名{/*生命块*/}     2.所有与该标记匹配的元素,都将应用声明块中的规则 二.类选择器     1.书写格式:.类名{/*声明块*/}         ...

  10. 基本库使用(urllib,requests)

    urllib(request,error,parse,robotparse) request模块 方法:urlopen()    {read(),readinto(),getheader(name), ...