Netty之粘包分包
粘包现象
客户端在一个for循环内连续发送1000个hello给Netty服务器端,
Socket socket = new Socket("127.0.0.1", 10101);
for(int i = 0; i < 1000; i++){
socket.getOutputStream().write(“hello”.getBytes());
}
socket.close();
而在服务器端接受到的信息并不是预期的1000个独立的Hello字符串.
实际上是无序的hello字符串混合在一起, 如图所示. 这种现象我们称之为粘包.

为什么会出现这种现象呢? TCP是个”流”协议,流其实就是没有界限的一串数据。
TCP底层中并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包划分,
所以在TCP中就有可能一个完整地包会被TCP拆分成多个包,也有可能吧多个小的包封装成一个大的数据包发送。
分包处理
顾名思义, 我们要对传输的数据进行分包. 一个简单的处理逻辑是在发送数据包之前, 先用四个字节占位, 表示数据包的长度.
数据包结构为:
| 长度(4字节) | 数据 |
Socket socket = new Socket("127.0.0.1", 10101);
String message = "hello";
byte[] bytes = message.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(4 + bytes.length);
// 消息长度
buffer.putInt(bytes.length);
// 消息正文
buffer.put(bytes);
byte[] array = buffer.array();
for(int i = 0; i < 1000; i++){
socket.getOutputStream().write(array);
}
socket.close();
服务器端代码, 我们需要借助于FrameDecoder类来分包.
public class MyDecoder extends FrameDecoder {
@Override
protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception {
if(buffer.readableBytes() > 4){
//标记
buffer.markReaderIndex();
//长度
int length = buffer.readInt();
if(buffer.readableBytes() < length){
buffer.resetReaderIndex();
//缓存当前剩余的buffer数据,等待剩下数据包到来
return null;
}
//读数据
byte[] bytes = new byte[length];
buffer.readBytes(bytes);
//往下传递对象
return new String(bytes);
}
//缓存当前剩余的buffer数据,等待剩下数据包到来
return null;
}
}
如此一来, 我们再次在服务器端接受到的消息就是按序打印的hello了.

这边可能有个疑问, 为什么MyDecoder中数据没有读取完毕, 需要return null,
正常的pipeline在数据处理完都是要sendUpstream, 给下一个pipeline的.
这个需要看下FrameDecoder.messageReceived 的源码. 他在其中缓存了一个cumulation对象,
如果return了null, 他会继续往缓存里写数据来实现分包
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
Object m = e.getMessage();
if (!(m instanceof ChannelBuffer)) {
// 数据读完了, 转下一个pipeline
ctx.sendUpstream(e);
} else {
ChannelBuffer input = (ChannelBuffer)m;
if (input.readable()) {
if (this.cumulation == null) {
try {
this.callDecode(ctx, e.getChannel(), input, e.getRemoteAddress());
} finally {
this.updateCumulation(ctx, input);
}
} else {
// 缓存上一次没读完整的数据
input = this.appendToCumulation(input);
try {
this.callDecode(ctx, e.getChannel(), input, e.getRemoteAddress());
} finally {
this.updateCumulation(ctx, input);
}
}
}
}
}
那么是不是这样就万事大吉了呢?
Socket字节流攻击
在上述代码中, 我们会在服务器端为客户端发送的数据包长度, 预先分配byte数组.
如果遇到恶意攻击, 传入的数据长度与内容 不匹配. 例如声明数据长度为Integer.MAX_VALUE.
这样会消耗大量的服务器资源生成byte[], 显然是不合理的.
因此我们还要加个最大长度限制.
if(buffer.readableBytes() > 2048){
buffer.skipBytes(buffer.readableBytes());
}
新的麻烦也随之而来, 虽然可以跳过指定长度, 但是数据包本身就乱掉了.
因为长度和内容不匹配, 跳过一个长度后, 不知道下一段数据的开头在哪里了.
因此我们自定义数据包里面, 不仅要引入数据包长度, 还要引入一个包头来划分各个包的范围.
包头用任意一段特殊字符标记即可, 例如$$$.
// 防止socket字节流攻击
if(buffer.readableBytes() > 2048){
buffer.skipBytes(buffer.readableBytes());
}
// 记录包头开始的index
int beginReader = buffer.readerIndex(); while(true) {
if(buffer.readInt() == ConstantValue.FLAG) {
break;
}
}
新的数据包结构为:
| 包头(4字节) | 长度(4字节) | 数据 |
Netty自带拆包类
自己实现拆包虽然可以细粒度控制, 但是也会有些不方便, 可以直接调用Netty提供的一些内置拆包类.
- FixedLengthFrameDecoder 按照特定长度组包
- DelimiterBasedFrameDecoder 按照指定分隔符组包, 例如本文中的$$$
- LineBasedFrameDecoder 按照换行符进行组包, \r \n等等
- ......
Netty之粘包分包的更多相关文章
- netty之粘包分包的处理
1.netty在进行字节数组传输的时候,会出现粘包和分包的情况.当个数据还好,如果数据量很大.并且不间断的发送给服务器,这个时候就会出现粘包和分包的情况. 2.简单来说:channelBuffer在接 ...
- netty解决粘包半包问题
前言:开发者用到TCP/IP交互时,偶尔会遇到粘包或者半包的数据,这种情况有时会对我们的程序造成严重的影响,netty框架为解决这种问题提供了若干框架 1. LineBasedFrameDecoder ...
- 服务端NETTY 客户端非NETTY处理粘包和拆包的问题
之前为了调式和方便一直没有处理粘包的问题,今天专门花了时间来搞NETTY的粘包处理,要知道在高并发下,不处理粘包是不可能的,数据流的混乱会造成业务的崩溃什么的我就不说了.所以这个问题 在我心里一直是个 ...
- Netty 拆包粘包和服务启动流程分析
Netty 拆包粘包和服务启动流程分析 通过本章学习,笔者希望你能掌握EventLoopGroup的工作流程,ServerBootstrap的启动流程,ChannelPipeline是如何操作管理Ch ...
- 【转】Netty 拆包粘包和服务启动流程分析
原文:https://www.cnblogs.com/itdragon/archive/2018/01/29/8365694.html Netty 拆包粘包和服务启动流程分析 通过本章学习,笔者希望你 ...
- Netty入门系列(2) --使用Netty解决粘包和拆包问题
前言 上一篇我们介绍了如果使用Netty来开发一个简单的服务端和客户端,接下来我们来讨论如何使用解码器来解决TCP的粘包和拆包问题 TCP为什么会粘包/拆包 我们知道,TCP是以一种流的方式来进行网络 ...
- netty拆包粘包
客户端 tcp udp socket网络编程接口 http/webservice mqtt/xmpp 自定义RPC (dubbo) 应用层 服务端 ServerSocket ss = new serv ...
- Netty解决粘包和拆包问题的四种方案
在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...
- 如何基于Netty处理粘包、拆包问题?
涉及到相关重要组件: ByteToMessageDecoder MessageToMessageDecoder 这两个组件都实现了ChannelInboundHandler接口,这说明这两个组件都是用 ...
随机推荐
- CodeForces - 385C Bear and Prime Numbers (埃氏筛的美妙用法)
Recently, the bear started studying data structures and faced the following problem. You are given a ...
- CSS3中的属性介绍
概念:给文字添加阴影 语法:text-shadow: length length length color 介绍:前两个参数为阴影离开文字的横方向位移距离与纵方向位移距离.使用时前两个参数必须指定,也 ...
- NoSQL:redis缓存数据库
一 Redis介绍 Redis和Memcached类似,也属于key-value nosql 数据库 Redis官网redis.io, 当前最新稳定版4.0.1 和Memcached类似,它支持存储的 ...
- Bean property属性说明
来自为知笔记(Wiz)
- Django总结
Django 中提供了开发网站经常用到的模块,常见的代码都为你写好了,通过减少重复的代码,Django 使你能够专注于 web 应用上有 趣的关键性的东西.为了达到这个目标,Django 提供了通用W ...
- Spark之MapReduce原理
参考http://www.cnblogs.com/wuyudong/p/mapreduce-principle.html MapReduce 我们来拆开看: Mapping(映射)对集合里的每个目 ...
- IE6常见CSS解析Bug及hack
IE6常见CSS兼容问题总结 1)图片间隙 A)div中的图片间隙(该bug出现在IE6及更低版本中) 描述:在div中插入图片时,图片会将div下方撑大三像素. hack1:将</div> ...
- 使用swiper简单的h5下滑翻页效果,
<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8" ...
- Nginx+Tomcat+Redis实现持久会话
使用开源web应用solo blog进行项目演示.前端使用Nginx作为负载均衡器,后端Tomcat连接Redis实现session存储.Redis的特点就是可以将session持久化.样才能真正实现 ...
- 社群公会GangSDK:程序员入行AI领域需要哪些技能?
作为一名Android开发工程师,身边总有些同行很焦虑,看着人工智能越来越火,总是担心Android要不行了,所以,我们需要转行么?Android还能走多久?其实,无论是对于Android还是iOS开 ...