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接口,这说明这两个组件都是用 ...
随机推荐
- 在vue2.0中使用sass
第一步:使用sass必须安装下面三个东西 cnpm install node-sass --save //安装node-sass cnpm install sass-loader --save //安 ...
- MQTT——连接报文
学习MQTT协议.如果只是看了相关文档就认为可以了.那是一个错误的观念.笔者为了能更好的去理解MQTT协议.看了不少相关的开源Broker的项目.可惜这些项目一般都是不完全的.不过从这些项目中笔者至少 ...
- Jimmychoo商城系统总结
一.需求 1.游戏模块 ①在进入H5之前,首先有一个动态的探照灯的动效,然后由"淡出"效果到H5首页. ②在点击"开始游戏"之后会有一段动画演示游戏内容,然后滑 ...
- Maven合并多个war包的工程需要用到的插件
<build> <finalName>WebSite</finalName> <plugins> <!-- 配置war包合并的插件 --> ...
- mac电脑安装apache,不能启动
因为mac系统是自带apach的 如果安装正确还是不能启动,有可能是 mac电脑自带apache功能,与安装的apache冲突. 这样关闭Mac自带apach即可. mac电脑apache命令:重启a ...
- C#对话框的使用
[函数] <整型> MessageBox(<字符串> Text, <字符串> Title, <整型> nType,MessageBoxIcon);[函数 ...
- c#异步调用的几种方式
首先,我们分析一下异步处理的环境 需要在当前线程中获取返回值 不需要在当前线程中获取返回值,但是仍然需要对返回值做处理对于第1中情况,还可以继续细分 在当前线程中启动线程T,然后继续执行当前线程中的其 ...
- Spring 高级依赖注入方式
1.处理自动装配的歧义性 1.1 标记首选的bean 使用@Primary 来说明一个bean是首选的. @Component @Primary public class GuoRongCD im ...
- shell脚本 expect 实现自动登陆
vi auto_ssh.exp #!/usr/bin/expect set ipaddress "123.227.159.159" set passwd "你的密码& ...
- input取值区间实例
前端开发中经常会遇到 input 取值区间的需求,比如搜索的时候,价格区间,积分值区间 大致的需求为 input 的值必须是正整数,第二个 input 的值必须大于第一个 DOM结构如下: <d ...