Netty - 粘包和半包(上)
在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看。
定义
TCP 传输中,客户端发送数据,实际是把数据写入到了 TCP 的缓存中,粘包和半包也就会在此时产生。
客户端给服务端发送了两条消息ABC和DEF,服务端这边的接收会有多少种情况呢?有可能是一次性收到了所有的消息ABCDEF,有可能是收到了三条消息AB、CD、EF。
上面所说的一次性收到了所有的消息ABCDEF,类似于粘包。如果客户端发送的包的大小比 TCP 的缓存容量小,并且 TCP 缓存可以存放多个包,那么客户端和服务端的一次通信就可能传递了多个包,这时候服务端从 TCP 缓存就可能一下读取了多个包,这种现象就叫粘包。
上面说的后面那种收到了三条消息AB、CD、EF,类似于半包。如果客户端发送的包的大小比 TCP 的缓存容量大,那么这个数据包就会被分成多个包,通过 Socket 多次发送到服务端,服务端第一次从接受缓存里面获取的数据,实际是整个包的一部分,这时候就产生了半包(半包不是说只收到了全包的一半,是说收到了全包的一部分)。
产生原因
其实从上面的定义,我们就可以大概知道产生的原因了。
粘包的主要原因:
- 发送方每次写入数据 < 套接字(Socket)缓冲区大小
 - 接收方读取套接字(Socket)缓冲区数据不够及时
 
半包的主要原因:
- 发送方每次写入数据 > 套接字(Socket)缓冲区大小
 - 发送的数据大于协议的 MTU (Maximum Transmission Unit,最大传输单元),因此必须拆包
 
其实我们可以换个角度看待问题:
- 从
收发的角度看,便是一个发送可能被多次接收,多个发送可能被一次接收。 - 从
传输的角度看,便是一个发送可能占用多个传输包,多个发送可能共用一个传输包。 
根本原因,其实是
TCP 是流式协议,消息无边界。
(PS : UDP 虽然也可以一次传输多个包或者多次传输一个包,但每个消息都是有边界的,因此不会有粘包和半包问题。)
解决方法
就像上面说的,UDP 之所以不会产生粘包和半包问题,主要是因为消息有边界,因此,我们也可以采取类似的思路。
改成短连接
将 TCP 连接改成短连接,一个请求一个短连接。这样的话,建立连接到释放连接之间的消息即为传输的信息,消息也就产生了边界。
这样的方法就是十分简单,不需要在我们的应用中做过多修改。但缺点也就很明显了,效率低下,TCP 连接和断开都会涉及三次握手以及四次握手,每个消息都会涉及这些过程,十分浪费性能。
因此,并不推介这种方式。
封装成帧
封装成帧(Framing),也就是原本发送消息的单位是缓冲大小,现在换成了帧,这样我们就可以自定义边界了。一般有4种方式:
固定长度
这种方式下,消息边界也就是固定长度即可。
优点就是实现很简单,缺点就是空间有极大的浪费,如果传递的消息中大部分都比较短,这样就会有很多空间是浪费的。
因此,这种方式一般也是不推介的。
分隔符
这种方式下,消息边界也就是分隔符本身。
优点是空间不再浪费,实现也比较简单。缺点是当内容本身出现分割符时需要转义,所以无论是发送还是接受,都需要进行整个内容的扫描。
因此,这种方式效率也不是很高,但可以尝试使用。
专门的 length 字段
这种方式,就有点类似 Http 请求中的 Content-Length,有一个专门的字段存储消息的长度。作为服务端,接受消息时,先解析固定长度的字段(length字段)获取消息总长度,然后读取后续内容。
优点是精确定位用户数据,内容也不用转义。缺点是长度理论上有限制,需要提前限制可能的最大长度从而定义长度占用字节数。
因此,十分推介用这种方式。
其他方式
其他方式就各不相同了,比如 JSON 可以看成是使用{}是否成对。这些优缺点就需要大家在各自的场景中进行衡量了。
Netty 中的实现
Netty 支持上文所讲的封装成帧(Framing)中的前三种方式,简单介绍下:
| 方式 | 解码 | 编码 | 
|---|---|---|
| 固定长度 | FixedLengthFrameDecoder | 简单 | 
| 分割符 | DelimiterBasedFrameDecoder | 简单 | 
| 专门的 length 字段 | LengthFieldBasedFrameDecoder | LengthFieldPrepender | 
总结
今天主要介绍了粘包和半包问题、解决思路和 Netty 中的支持,我会在下一篇文章里重点讲述 Netty 中的具体实现,敬请期待。
有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。


Netty - 粘包和半包(上)的更多相关文章
- Netty - 粘包和半包(下)
		
上一篇介绍了粘包和半包及其通用的解决方案,今天重点来看一下 Netty 是如何实现封装成帧(Framing)方案的. 解码核心流程 之前介绍过三种解码器FixedLengthFrameDecoder. ...
 - TCP的粘包、半包和Netty的处理
		
参考文献:极客时间傅健老师的<Netty源码剖析与实战>Talk is cheap.show me the code! 什么是粘包和半包 在客户端发送数据时,实际是把数据写入到了TCP发送 ...
 - socket编程 TCP 粘包和半包 的问题及解决办法
		
一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系. 粘包 为x.5个包 半包 为0.5个包 由于网络原因 一次 ...
 - C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)
		
介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...
 - 关于TCP封包、粘包、半包
		
关于Tcp封包 很多朋友已经对此作了不少研究,也花费不少心血编写了实现代码和blog文档.当然也充斥着一些各式的评论,自己看了一下,总结一些心得. 首先我们学习一下这些朋友的心得,他们是: http: ...
 - 详说tcp粘包和半包
		
tcp服务端和客户端建立连接后会长时间维持这个连接,用于互相传递数据,tcp是以流的方式传输数据的,就像一个水管里的水一样,从一头不断的流向另一头. 理想情况下,发送的数据包都是独立的, 现实要复杂一 ...
 - c#  socket 解决粘包,半包
		
处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断 ...
 - TCP的组包、半包、粘包与分包
		
一.概念 1)组包.简单的说就是tcp协议把过大的数据包分成了几个小的包传输,接收方要把同一组的数据包重新组合成一个完整的数据包. 2)半包.指接受方没有接受到一个完整的包,只接受了部分,这种情况主要 ...
 - c# Socket通讯中关于粘包,半包的处理,加分割符
		
using System; using System.Collections.Generic; using System.Text; using System.Net.Sockets; using S ...
 
随机推荐
- Spring Boot2 系列教程(六)自定义 Spring Boot 中的 starter
			
我们使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中.Starter 为我们带来了众多的自动化配置,有了这些自动化配置,我们可以不费吹灰之力就能搭建一个生产级开发环境,有的小 ...
 - linux 假死分析
			
所谓假死,就是能ping通,但是ssh不上去:任何其他操作也都没反应,包括上面部署的apache也打不开页面. 作为一个多任务操作系统,要把系统忙死,忙到ssh都连不上去,也不是那么容易的.尤其是现在 ...
 - pip安装Mysql-python报错EnvironmentError: mysql_config not found
			
如下图,安装Mysql-python报错EnvironmentError: mysql_config not found 经过验证,可通过以下方式解决: 从官网下载mysql安装,成功之后输入PATH ...
 - Linux初识之Centos7中terminal光标位置偏移问题的解决
			
新安装的centos7打开terminal发现光标位置向右偏移,使用起来影响感官,经查询后找到类似情况并顺利解决问题,特记录解决过程以作参考. 1.未解决时光标向右偏移显示: 2.打开设置(Setti ...
 - 【转】在Linux下搭建Git服务器
			
在 Linux 下搭建 Git 服务器 环境: 服务器 CentOS6.6 + git(version 1.7.1)客户端 Windows10 + git(version 2.8.4.windows. ...
 - iOS 开发中一些 tips
			
tableView 的 tableHeaderView 高度不正确的问题: func forceRefreshHeader() { let size = headerView.systemLayout ...
 - kafka删除弃用的groupid
			
登录zookeeper客户端 cd zookeeper-3.4.10 sh zkCli.sh 查看groupid信息 [zk:localhost:2181(CONNECTED) 1] ls /cons ...
 - 工厂模式(整理自李建忠<C++设计模式>视频)
			
整理自李建忠<C++设计模式>视频 一.导入:"对象创建"模式和工厂模式 工厂模式只是该模式下的一种. 二.举例说明 有这样一个场景:需要在MainForm中设计一个按 ...
 - 对Servlet执行流程的初步认识
			
这里我们以Post方式请求Serclet为例 1.找到 中的URL地址 Form表单的Post请求HelloServlet(Action="HelloServlet")发起时, A ...
 - 记一次java-selenium自动抢红包最简单案例1
			
案例网址:http://xinyue.qq.com/act/pc/xyjf/a20170907envelopes/index.htm?ADTAG=AD_gw.home.pt.2_dyghb.20170 ...