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接口,这说明这两个组件都是用 ...
随机推荐
- [Bayesian] “我是bayesian我怕谁”系列 - Continuous Latent Variables
打开prml and mlapp发现这部分目录编排有点小不同,但神奇的是章节序号竟然都为“十二”. prml:pca --> ppca --> fa mlapp:fa --> pca ...
- 为Lua5.3编写C模块简单示例
为Lua5.3编写C模块简单示例 一.编译安装Lua5.3 MSVC 命令行安装脚本: @echo off md bin md lib md include cd src cl /c /nologo ...
- dynamic_cast 转换示例
dynamic_cast 转换示例 /* 带虚函数与不带虚函数转换的区别 dynamic_cast:必须要有虚函数才可以转换 dynamic_cast:只能处理转换指针或引用,不能转换对象 dynam ...
- Leetcode题解(十一)
31.Next Permutation 题目 这道题目的意思是给定一个序列,找出其按照字典顺序的下一个顺序,如果给定顺序是字典中的最后一个顺序,其下一个顺序则为字典中的第一个顺序.比如: 1,2,3, ...
- 网络基础四 DNS DHCP 路由 FTP
第1章 网络基础 1.1 IP地址分类 IP地址的类别-按IP地址数值范围划分 IP地址的类别-按IP地址用途分类 IP地址的类别-按网络通信方式划分 1.2 局域网上网原理过程 DHCP原理过程详情 ...
- Linux必知必会的目录与启动过程
第1章 /etc/目录 1.1 /etc/sysconfig/network-scripts/ifcfg-eth0 linux第一块网卡的配置文件 [root@znix ~]# cat /etc/sy ...
- Windows解决anaconda下双python版本安装TensorFlow
首先,就是双版本anaconda的安装: 以前安装好的是python2.7版本,而TensorFlow的安装仅支持3.5版本的.但是自己本来的2.7版本又不想遗弃.所以安装双版本的: 在anacond ...
- JavaScript系列----AJAX机制详解以及跨域通信
1.Ajax 1.1.Ajax简介 Ajax简介这一部分我们主要是谈一下ajax的起源,ajax是什么?因为这些是跟技术无关的.所以,大多细节都是一笔带过. Ajax的起源? Ajax一词源于2005 ...
- 如何兼容所有Android版本选择照片或拍照然后裁剪图片--基于FileProvider和动态权限的实现
我们知道, Android操作系统一直在进化. 虽然说系统是越来越安全, 可靠, 但是对于开发者而言, 开发难度是越来越大的, 需要注意的兼容性问题, 也越来越多. 就比如在Android平台上拍照或 ...
- Python学习--列表和元组
在python中,最基本的数据结构是序列.序列中的每个元素被分配一个序号--即元素的位置,也称为索引.第一个索引是0. python包含6种内建的序列:列表.元组.字符串.Unicode字符串.buf ...