粘包现象


客户端在一个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之粘包分包的更多相关文章

  1. netty之粘包分包的处理

    1.netty在进行字节数组传输的时候,会出现粘包和分包的情况.当个数据还好,如果数据量很大.并且不间断的发送给服务器,这个时候就会出现粘包和分包的情况. 2.简单来说:channelBuffer在接 ...

  2. netty解决粘包半包问题

    前言:开发者用到TCP/IP交互时,偶尔会遇到粘包或者半包的数据,这种情况有时会对我们的程序造成严重的影响,netty框架为解决这种问题提供了若干框架 1. LineBasedFrameDecoder ...

  3. 服务端NETTY 客户端非NETTY处理粘包和拆包的问题

    之前为了调式和方便一直没有处理粘包的问题,今天专门花了时间来搞NETTY的粘包处理,要知道在高并发下,不处理粘包是不可能的,数据流的混乱会造成业务的崩溃什么的我就不说了.所以这个问题 在我心里一直是个 ...

  4. Netty 拆包粘包和服务启动流程分析

    Netty 拆包粘包和服务启动流程分析 通过本章学习,笔者希望你能掌握EventLoopGroup的工作流程,ServerBootstrap的启动流程,ChannelPipeline是如何操作管理Ch ...

  5. 【转】Netty 拆包粘包和服务启动流程分析

    原文:https://www.cnblogs.com/itdragon/archive/2018/01/29/8365694.html Netty 拆包粘包和服务启动流程分析 通过本章学习,笔者希望你 ...

  6. Netty入门系列(2) --使用Netty解决粘包和拆包问题

    前言 上一篇我们介绍了如果使用Netty来开发一个简单的服务端和客户端,接下来我们来讨论如何使用解码器来解决TCP的粘包和拆包问题 TCP为什么会粘包/拆包 我们知道,TCP是以一种流的方式来进行网络 ...

  7. netty拆包粘包

    客户端 tcp udp socket网络编程接口 http/webservice mqtt/xmpp 自定义RPC (dubbo) 应用层 服务端 ServerSocket ss = new serv ...

  8. Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

  9. 如何基于Netty处理粘包、拆包问题?

    涉及到相关重要组件: ByteToMessageDecoder MessageToMessageDecoder 这两个组件都实现了ChannelInboundHandler接口,这说明这两个组件都是用 ...

随机推荐

  1. 简单的一句sql

    表1 Id Name 1 张三 2 李四 3 王五 表二 Id Name1 Name2 1 1 2 2 2 1 3 2 3 4 1 3 我现在要查出结果如下: Id Name1 Name2 1 张三 ...

  2. 剑指offer--面试题3

    一 题目: 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.完成一个函数,输入这样的数组和一整数,判断这个数组是否包含这个整数. 二 分析 如果这个二维数组是 ...

  3. 树莓派链接WiFi设置

    树莓派摆脱网线的束缚 在看这篇文章之前,请确保自己树莓派OS已经正常安装且已经连接网线,然后准备一个无线网卡,开始实现我们的树莓派摆脱网线的束缚吧 ! -_- 一.    安装网卡驱动 1.      ...

  4. C# linq创建嵌套组

    以下示例演示如何在 LINQ 查询表达式中创建嵌套组. 首先根据学生年级创建每个组,然后根据每个人的姓名进一步细分为小组. public void QueryNestedGroups() { var ...

  5. css元素选择器 first-child nth-child

    E:first-child   只要E元素是它的父级的第一个子元素,就选中.它需要同时满足两个条件,    (1)是"第一个子元素",    (2)是"这个子元素刚好是E ...

  6. Browsing contexts 浏览器上下文

    浏览上下文就是document object 呈现给用户的所在的环境   每一个标签或者窗口都包含一个浏览器上下文,包括iframe   frames      每一个browsing context ...

  7. 项目实战1—LNMP的搭建、nginx反向代理和缓存等的实现

    实战一:搭建lnmp及类小米等商业网站的实现 环境:关闭防火墙,selinux 1.安装包,开启服务 yum -y install nginx mariadb-server php-fpm php-m ...

  8. JMeter基础教程3:脚本录制篇

    对于一些JMeter初学者来说,录制脚本可能是最容易掌握的技能之一.虽然我不建议录制性能脚本(因为录制的脚本比较混乱,必须要通过二次处理才可正常使用),但有时做总比不做要好,是吧?下面我们详细介绍使用 ...

  9. 由浅入深学习springboot中使用redis

    很多时候,我们会在springboot中配置redis,但是就那么几个配置就配好了,没办法知道为什么,这里就详细的讲解一下 这里假设已经成功创建了一个springboot项目. redis连接工厂类 ...

  10. C#用panel实现子窗体的切换

    今天编程的时候,遇到一个问题:在同一个窗体区域加载两个不同的窗体,每次只显示一个子窗体并能够对这两个子窗体做切换. 对于这个问题用panel控件是非常简单的,只要每次清空panel控件上的子窗体,然后 ...