在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看。

定义

TCP 传输中,客户端发送数据,实际是把数据写入到了 TCP 的缓存中,粘包和半包也就会在此时产生。

客户端给服务端发送了两条消息ABCDEF,服务端这边的接收会有多少种情况呢?有可能是一次性收到了所有的消息ABCDEF,有可能是收到了三条消息ABCDEF

上面所说的一次性收到了所有的消息ABCDEF,类似于粘包。如果客户端发送的包的大小比 TCP 的缓存容量小,并且 TCP 缓存可以存放多个包,那么客户端和服务端的一次通信就可能传递了多个包,这时候服务端从 TCP 缓存就可能一下读取了多个包,这种现象就叫粘包

上面说的后面那种收到了三条消息ABCDEF,类似于半包。如果客户端发送的包的大小比 TCP 的缓存容量大,那么这个数据包就会被分成多个包,通过 Socket 多次发送到服务端,服务端第一次从接受缓存里面获取的数据,实际是整个包的一部分,这时候就产生了半包(半包不是说只收到了全包的一半,是说收到了全包的一部分)。

产生原因

其实从上面的定义,我们就可以大概知道产生的原因了。

粘包的主要原因:

  1. 发送方每次写入数据 < 套接字(Socket)缓冲区大小
  2. 接收方读取套接字(Socket)缓冲区数据不够及时

半包的主要原因:

  1. 发送方每次写入数据 > 套接字(Socket)缓冲区大小
  2. 发送的数据大于协议的 MTU (Maximum Transmission Unit,最大传输单元),因此必须拆包

其实我们可以换个角度看待问题:

  1. 收发的角度看,便是一个发送可能被多次接收,多个发送可能被一次接收。
  2. 传输的角度看,便是一个发送可能占用多个传输包,多个发送可能共用一个传输包。

根本原因,其实是

TCP 是流式协议,消息无边界。

(PS : UDP 虽然也可以一次传输多个包或者多次传输一个包,但每个消息都是有边界的,因此不会有粘包和半包问题。)

解决方法

就像上面说的,UDP 之所以不会产生粘包和半包问题,主要是因为消息有边界,因此,我们也可以采取类似的思路。

改成短连接

将 TCP 连接改成短连接,一个请求一个短连接。这样的话,建立连接到释放连接之间的消息即为传输的信息,消息也就产生了边界。

这样的方法就是十分简单,不需要在我们的应用中做过多修改。但缺点也就很明显了,效率低下,TCP 连接和断开都会涉及三次握手以及四次握手,每个消息都会涉及这些过程,十分浪费性能。

因此,并不推介这种方式。

封装成帧

封装成帧(Framing),也就是原本发送消息的单位是缓冲大小,现在换成了帧,这样我们就可以自定义边界了。一般有4种方式:

固定长度

这种方式下,消息边界也就是固定长度即可。

优点就是实现很简单,缺点就是空间有极大的浪费,如果传递的消息中大部分都比较短,这样就会有很多空间是浪费的。

因此,这种方式一般也是不推介的。

分隔符

这种方式下,消息边界也就是分隔符本身。

优点是空间不再浪费,实现也比较简单。缺点是当内容本身出现分割符时需要转义,所以无论是发送还是接受,都需要进行整个内容的扫描。

因此,这种方式效率也不是很高,但可以尝试使用。

专门的 length 字段

这种方式,就有点类似 Http 请求中的 Content-Length,有一个专门的字段存储消息的长度。作为服务端,接受消息时,先解析固定长度的字段(length字段)获取消息总长度,然后读取后续内容。

优点是精确定位用户数据,内容也不用转义。缺点是长度理论上有限制,需要提前限制可能的最大长度从而定义长度占用字节数。

因此,十分推介用这种方式。

其他方式

其他方式就各不相同了,比如 JSON 可以看成是使用{}是否成对。这些优缺点就需要大家在各自的场景中进行衡量了。

Netty 中的实现

Netty 支持上文所讲的封装成帧(Framing)中的前三种方式,简单介绍下:

方式 解码 编码
固定长度 FixedLengthFrameDecoder 简单
分割符 DelimiterBasedFrameDecoder 简单
专门的 length 字段 LengthFieldBasedFrameDecoder LengthFieldPrepender

总结

今天主要介绍了粘包和半包问题、解决思路和 Netty 中的支持,我会在下一篇文章里重点讲述 Netty 中的具体实现,敬请期待。

有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。

https://death00.github.io/

Netty - 粘包和半包(上)的更多相关文章

  1. Netty - 粘包和半包(下)

    上一篇介绍了粘包和半包及其通用的解决方案,今天重点来看一下 Netty 是如何实现封装成帧(Framing)方案的. 解码核心流程 之前介绍过三种解码器FixedLengthFrameDecoder. ...

  2. TCP的粘包、半包和Netty的处理

    参考文献:极客时间傅健老师的<Netty源码剖析与实战>Talk is cheap.show me the code! 什么是粘包和半包 在客户端发送数据时,实际是把数据写入到了TCP发送 ...

  3. socket编程 TCP 粘包和半包 的问题及解决办法

    一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系. 粘包 为x.5个包 半包 为0.5个包 由于网络原因 一次 ...

  4. C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

    介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...

  5. 关于TCP封包、粘包、半包

    关于Tcp封包 很多朋友已经对此作了不少研究,也花费不少心血编写了实现代码和blog文档.当然也充斥着一些各式的评论,自己看了一下,总结一些心得. 首先我们学习一下这些朋友的心得,他们是: http: ...

  6. 详说tcp粘包和半包

    tcp服务端和客户端建立连接后会长时间维持这个连接,用于互相传递数据,tcp是以流的方式传输数据的,就像一个水管里的水一样,从一头不断的流向另一头. 理想情况下,发送的数据包都是独立的, 现实要复杂一 ...

  7. c# socket 解决粘包,半包

    处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断 ...

  8. TCP的组包、半包、粘包与分包

    一.概念 1)组包.简单的说就是tcp协议把过大的数据包分成了几个小的包传输,接收方要把同一组的数据包重新组合成一个完整的数据包. 2)半包.指接受方没有接受到一个完整的包,只接受了部分,这种情况主要 ...

  9. c# Socket通讯中关于粘包,半包的处理,加分割符

    using System; using System.Collections.Generic; using System.Text; using System.Net.Sockets; using S ...

随机推荐

  1. JAVA学习笔记—review基本知识[反射与异常]

    JAVA学习笔记—review基本知识[反射与异常] 1.异常: 1.1异常的分类: Java会将所有的异常封装成对象,其根本父类为Throwable. Throwable有两个子类:Error 和E ...

  2. stm32 新建工程

    先新建六个文件夹. Consis:启动文件等 Fwlib:inc.src文件夹 Hardware:存放驱动 Mdk:工程文件 User:main函数等

  3. Android Studio 3.1.3填坑之路

      昨天编写程序的时候,遇到了一个非常令人头疼的BUG,如下图:   标题栏和里面的内容都消失了,这对于一个非常在乎排版的软件来说简直就是晴空霹雳,搞了好长时间,终于在今天找到解决方法,原来是升级搞的 ...

  4. Spring Boot 入门之基础构建篇(一)

    博客地址:http://www.moonxy.com 一.前言 随着 Spring 的功能越来越强,在使用 Spring 的时候,门槛也变得高了起来,诸如搭建一个基于 Spring 的 Web 程序却 ...

  5. Day 6 文件属性与命令执行流程

    1. 第一列第一个字符 表示文件类型 rw-r--r--     权限(下周) 4 这个文件被链接次数 root 文件的拥有者(用户) root 文件的拥有组(用户组 ==>家族) 2018 文 ...

  6. Day4 文件管理-常用命令

    文件管理 --> 创建 移动 删除 复制 1.cp复制: #####-v:详细显示命令执行的操作 #####-r: 递归处理目录与子目录 #####-p: 保留源文件或目录的属性 #####1. ...

  7. Google、百度搜索引擎高级应用6/15

    不得不知道的搜索技巧.

  8. hadoop生态系列

    1.hadoop高可用安装和原理详解 2.hadoop2.7+spark2.2+zookeeper3.4.简单安装 3.windows下通过idea连接hadoop和spark集群 4.hadoop2 ...

  9. 网络编程之多线程——GIL全局解释器锁

    网络编程之多线程--GIL全局解释器锁 一.引子 定义: In CPython, the global interpreter lock, or GIL, is a mutex that preven ...

  10. 第八届蓝桥杯java b组第五题

    标题:取数位 求1个整数的第k位数字有很多种方法.以下的方法就是一种. 对于题目中的测试数据,应该打印5. 请仔细分析源码,并补充划线部分所缺少的代码. 注意:只提交缺失的代码,不要填写任何已有内容或 ...