Netty(4)Stream by codec(粘包与拆包)
TCP/IP,传输的是byte[],将byte[]放入队列中。可能会发生粘包和拆包。
比如,客户端向服务端发送了2条消息,分别为D1,D2,可能产生的情况,如下图所示:

情况一:正常的。
情况二:粘包。
情况三:拆包。即:部分数据不是一次完整发送的,而是分了至少2次发送。
如本例,D2拆成了D2_1和D2_2,这是拆包。
服务端分2次收到包,第一次收到了D1和D2_1包,这是粘包;服务端第二次收到了D2_2包,这是拆包。
回到Time client例子,存在相同的问题。4字节的int很小,很少发生粘包或拆包。但是,如果并发量大时,可能会发生。
最简单的方法是创建一个内部全局的(只为了多次接收放入相同buffer)buffer,等待,直到4个字节全部接受。以下修改了TimeClientHandler解决此问题。
第一种解决方法:全局buffer,累积。
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.date.DateUtil;
@Slf4j
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private ByteBuf buf; @Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
buf = ctx.alloc().buffer();//()
} @Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
buf.release();//()
buf = null;
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf m = (ByteBuf) msg;
buf.writeBytes(m);//()
m.release();
if (buf.readableBytes() >= ) {//()
long currentTimeMillis = (buf.readUnsignedInt() - 2208988800L)*1000L;
log.info("{}",DateUtil.date(currentTimeMillis));
ctx.close();
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
} }
1、ChannelHandler
- ChannelHandler有2个监听器方法(生命周期):handlerAdded()和handlerRemoved()。当该handler被添加到pipeline中时,被触发;当该handler从pipeline删除时,被触发。该handler,执行顺序:channelAdd()--->channelRead()--->channelRemove()。
- 只要任务不重,即不会阻塞很久,可以在这2个方法中做一些初始化的工作。比如本例,channelAdd()中初始分配一个4字节的ByteBuf,在channelRemove()中释放ByteBuf。
2、首先,接收到的所有字节均被写入buf。
3、然后,handler必须要检查是否够4个字节(本例),如果不够(拆包),则当有剩下的数据来时,Netty会再次调用该channelRead()方法,直到4个字节都接收到为止。
第二种解决方法:使用解码器
尽管第一种方法解决了粘包和拆包问题,但是,代码臃肿。因为,可以向pipeline中添加多个handler,因此,我们可以将TimeClientHandler分割成2个handler:
1)、TimeDecoder
2)、上节里的TimeClientHandler版本。
TimeDecoder.java
import java.util.List; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; public class TimeDecoder extends ByteToMessageDecoder {//(1)
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {//(2)
if (in.readableBytes() < 4) {
return ;//(3)
}
out.add(in.readBytes(4));//(4)
}
}
1、ByteToMessageDecoder 实现了ChannelInboundHandler

2、ByteToMessageDecoder,当调用decode()时,其内部实现了“累积”功能的buffer,即不用自己在写全局buffer了,当接收到新数据时,会向该buffer中写入。
3、decode():比如,一个包分2次传的,则会调用2次decode()。第一次即使不够4个字节,也会存入其内部“累积”buffer。我们的decode()方法中,return即可。
4、decode()中,一旦添加了一个obj到“out”,意味着该decoder已经成功将消息解码了,即解决了粘包拆包问题。此时,ByteToMessageDecoder将会丢弃掉“累积”buffer中已读的消息。ByteToMessageDecoder将会不断调用decode(),直到添加“空”到out为止。
TimeClientHandler.java(上节time中的TimeClientHandler)
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.date.DateUtil;
@Slf4j
public class TimeClientHandler extends ChannelInboundHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf m = (ByteBuf) msg;
try {
long currentTimeMillis = (m.readUnsignedInt() - 2208988800L)*1000L;
log.info("{}",DateUtil.date(currentTimeMillis));
ctx.close();
} finally {
m.release();
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
} }
然后,
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeDecoder(),new TimeClientHandler());
}
执行顺序:TimeDecoder.decode(ctx, ByteBuf in, List<Object> out)--->TimeClientHandler.channelRead(ctx, Object msg)。
上例中,从TimeDecoder传给TimeClientHandler的依然是ByteBuf,既然是int,那我们可以直接传递int吗?可以,如下:
TimeDecoder.java,修改成
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {//(2)
if (in.readableBytes() < 4) {
return ;//(3)
}
//out.add(in.readBytes(4));//(4)
out.add(in.readUnsignedInt());//(4)
}
TimeClientHandler.java,修改成
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
long m = (long) msg;
long currentTimeMillis = (m - 2208988800L)*1000L;
log.info("{}",DateUtil.date(currentTimeMillis));
ctx.close();
}
ReplayingDecoder是一个更加简单的decoder。可以代替ByteToMessageDecoder,只需要修改TimeDecoder,如下:
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder; import java.util.List; public class TimeDecoder extends ReplayingDecoder<Void> {//(1)
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {//(2)
out.add(in.readUnsignedInt());
}
}
其他代码都不用动。
最终输出:
服务端:18:43:45.824 [nioEventLoopGroup-3-4] -549056671
客户端:
18:43:45.854 [nioEventLoopGroup-2-1] 2018-09-14 18:43:45
18:43:45.929 [main] client channel is closed.
Netty(4)Stream by codec(粘包与拆包)的更多相关文章
- 如何基于Netty处理粘包、拆包问题?
涉及到相关重要组件: ByteToMessageDecoder MessageToMessageDecoder 这两个组件都实现了ChannelInboundHandler接口,这说明这两个组件都是用 ...
- netty 解决TCP粘包与拆包问题(二)
TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...
- netty 解决TCP粘包与拆包问题(一)
1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...
- 【Netty】TCP粘包和拆包
一.前言 前面已经基本上讲解完了Netty的主要内容,现在来学习Netty中的一些可能存在的问题,如TCP粘包和拆包. 二.粘包和拆包 对于TCP协议而言,当底层发送消息和接受消息时,都需要考虑TCP ...
- Netty(二)——TCP粘包/拆包
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...
- Netty使用LineBasedFrameDecoder解决TCP粘包/拆包
TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...
- Netty 中的粘包和拆包
Netty 底层是基于 TCP 协议来处理网络数据传输.我们知道 TCP 协议是面向字节流的协议,数据像流水一样在网络中传输那何来 "包" 的概念呢? TCP是四层协议不负责数据逻 ...
- Netty(三)TCP粘包拆包处理
tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...
- Netty解决粘包和拆包问题的四种方案
在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...
随机推荐
- listen 55
There are also green card qualifiers for some non-citizens who invest in America, and for refugees.难 ...
- python2.7 爬虫初体验爬取新浪国内新闻_20161130
python2.7 爬虫初学习 模块:BeautifulSoup requests 1.获取新浪国内新闻标题 2.获取新闻url 3.还没想好,想法是把第2步的url 获取到下载网页源代码 再去分析源 ...
- Swift协议
「协议」(protocol)声明一系列方法.属性.下标等用来约束其「遵循者」,进而保证「遵循者」能够完成限定的工作.「协议」本身不实现任何功能,它仅仅描述了「遵循者」的实现.「协议」能被类.结构体.枚 ...
- mysql数据库---编码格式基本操作
1.查看数据库编码格式 mysql> show variables like 'character_set_database'; 2.查看数据表的编码格式 mysql> show crea ...
- bzoj 4104 解密运算 —— 思路
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4104 一开始发现了给出的顺序是按这些末尾字符后面的后缀排序得到的: 然后发现可以一个一个把字 ...
- POJ3067(树状数组:统计数字出现个数)
Japan Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 24151 Accepted: 6535 Descriptio ...
- sqlServer对内存的管理
简介 理解SQL Server对于内存的管理是对于SQL Server问题处理和性能调优的基本,本篇文章讲述SQL Server对于内存管理的内存原理. 二级存储(secondary storage) ...
- 如何在模板类中使用这些point类型?
博客转载自:http://www.pclcn.org/study/shownews.php?lang=cn&id=271 由于PCL模块较多,并且是一个模板库,在一个源文件里包含很多PCL算法 ...
- g2o中setparameterid(0,0)方法
其中两个参数的含义: 第二个参数是优化器内添加的参数的id.当你调用addEdge来添加这条边时,会根据第二个参数的id,把相应的参数地址给边,以后边内的成员函数,就根据第一个参数,拿到这个地址.
- POJ 3255 Roadblocks (次短路)
题意:给定一个图,求一条1-n的次短路. 析:次短路就是最短路再长一点呗,我们可以和求最短路一样,再多维护一个数组,来记录次短路. 代码如下: #pragma comment(linker, &quo ...