zoukankan      html  css  js  c++  java
  • Netty

    在网络传输中,粘包和半包应该是最常出现的问题,作为 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/

  • 相关阅读:
    ubuntu关闭和开启防火墙
    supervisor 从安装到使用
    suse11/12关闭防火墙
    [Linux实用工具]Linux监控工具munin的展示(Nginx)
    [Linux实用工具]Linux监控工具munin的安装和配置
    [Linux]阿里云免费试用体验(在阿里云的ubuntu上部署个人服务)
    [Linux基础环境/软件]Linux下安装resin web服务器(涉及gcc、jdk环境部署)
    [Linux基础环境/软件]Linux下安装mysql
    [Linux实用工具]Windows下同步Linux文件(Linux安装Samba和配置)
    [Linux实用工具]Ubuntu环境下SSH的安装及使用
  • 原文地址:https://www.cnblogs.com/death00/p/11725918.html
Copyright © 2011-2022 走看看