Netty粘包拆包问题解决方案

TCP黏包拆包

TCP是一个流协议,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
怎么解决?

  • • 消息定长度,传输的数据大小固定长度,例如每段的长度固定为100字节,如果不够空位补空格
  • • 在数据包尾部添加特殊分隔符,比如下划线,中划线等
  • • 将消息分为消息头和消息体,消息头中包含表示信息的总长度

Netty提供了多个解码器,可以进行分包的操作,分别是:

  • • LineBasedFrameDecoder (回车换行分包)
  • • DelimiterBasedFrameDecoder(特殊分隔符分包)
  • • FixedLengthFrameDecoder(固定长度报文来分包)
  • • LengthFieldBasedFrameDecoder(自定义长度来分包)

制造粘包和拆包问题

为了验证我们的解码器能够解决这种粘包和拆包带来的问题,首先我们就制造一个这样的问题,以此用来做对比。
服务端:

public static void main(String[] args) {
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    ServerBootstrap bootstrap = new ServerBootstrap();
    bootstrap.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {
          @Override
          public void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline().addLast("decoder", new StringDecoder());
            ch.pipeline().addLast("encoder", new StringEncoder());
            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
              @Override
              public void channelRead(ChannelHandlerContext ctx, Object msg) {
                System.err.println("server:" + msg.toString());
                ctx.writeAndFlush(msg.toString() + "你好" );
              }
            });
          }
        })
        .option(ChannelOption.SO_BACKLOG, 128)
        .childOption(ChannelOption.SO_KEEPALIVE, true);
    try {
      ChannelFuture f = bootstrap.bind(2222).sync();
       f.channel().closeFuture().sync();
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      workerGroup.shutdownGracefully();
      bossGroup.shutdownGracefully();
    }
  }

客户端我们发送一个比较长的字符串,如果服务端收到的消息是一条,那么就是对的,如果是多条,那么就有问题了。

public static void main(String[] args) {
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    Channel channel = null;
    try {
      Bootstrap b = new Bootstrap();
      b.group(workerGroup);
      b.channel(NioSocketChannel.class);
      b.option(ChannelOption.SO_KEEPALIVE, true);
      b.handler(new ChannelInitializer<SocketChannel>() {
        @Override
        public void initChannel(SocketChannel ch) throws Exception {
          ch.pipeline().addLast("decoder", new StringDecoder());
          ch.pipeline().addLast("encoder", new StringEncoder());
          ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) {
              System.err.println("client:" + msg.toString());
            }
          });
        }
      });
      ChannelFuture f = b.connect("127.0.0.1", 2222).sync();
      channel = f.channel();
      StringBuilder msg = new StringBuilder();
      for (int i = 0; i < 100; i++) {
        msg.append("hello yinjihuan");
      }
      channel.writeAndFlush(msg);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }

首先启动服务端,然后再启动客户端,通过控制台可以看到服务接收的数据分成了2次,这就是我们要解决的问题。

server:hello yinjihuanhello....
server:o yinjihuanhello...

LineBasedFrameDecoder

用LineBasedFrameDecoder 来解决需要在发送的数据结尾加上回车换行符,这样LineBasedFrameDecoder 才知道这段数据有没有读取完整。

改造服务端代码,只需加上LineBasedFrameDecoder 解码器即可,构造函数的参数是数据包的最大长度。

 public void initChannel(SocketChannel ch) throws Exception {
   ch.pipeline().addLast(new LineBasedFrameDecoder(10240));
   ch.pipeline().addLast("decoder", new StringDecoder());
   ch.pipeline().addLast("encoder", new StringEncoder());
   ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.err.println("server:" + msg.toString());
        ctx.writeAndFlush(msg.toString() + "你好");
      }
   });
}

改造客户端发送代码,再数据后面加上回车换行符

ChannelFuture f = b.connect("127.0.0.1", 2222).sync();
channel = f.channel();
StringBuilder msg = new StringBuilder();
for (int i = 0; i < 100; i++) {
  msg.append("hello yinjihuan");
}
channel.writeAndFlush(msg + System.getProperty("line.separator"));

DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder和LineBasedFrameDecoder差不多,DelimiterBasedFrameDecoder可以自己定义需要分割的符号,比如下划线,中划线等等。
改造服务端代码,只需加上DelimiterBasedFrameDecoder解码器即可,构造函数的参数是数据包的最大长度。我们用下划线来分割。

public void initChannel(SocketChannel ch) throws Exception {
   ch.pipeline().addLast(new DelimiterBasedFrameDecoder(10240, Unpooled.copiedBuffer("_".getBytes())));
   ch.pipeline().addLast("decoder", new StringDecoder());
   ch.pipeline().addLast("encoder", new StringEncoder());
   ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.err.println("server:" + msg.toString());
        ctx.writeAndFlush(msg.toString() + "你好");
      }
   });
}

改造客户端发送代码,再数据后面加上下划线

ChannelFuture f = b.connect("127.0.0.1", 2222).sync();
channel = f.channel();
StringBuilder msg = new StringBuilder();
for (int i = 0; i < 100; i++) {
  msg.append("hello yinjihuan");
}
channel.writeAndFlush(msg + "_");

FixedLengthFrameDecoder

FixedLengthFrameDecoder是按固定的数据长度来进行解码的,也就是说你客户端发送的每条消息的长度是固定的,下面我们看看怎么使用。

服务端还是一样,增加FixedLengthFrameDecoder解码器即可。

 public void initChannel(SocketChannel ch) throws Exception {
   ch.pipeline().addLast(new FixedLengthFrameDecoder(1500));
   ch.pipeline().addLast("decoder", new StringDecoder());
   ch.pipeline().addLast("encoder", new StringEncoder());
   ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.err.println("server:" + msg.toString());
        ctx.writeAndFlush(msg.toString() + "你好");
      }
   });
}

客户端,msg输出的长度就是1500

ChannelFuture f = b.connect("127.0.0.1", 2222).sync();
channel = f.channel();
StringBuilder msg = new StringBuilder();
for (int i = 0; i < 100; i++) {
  msg.append("hello yinjihuan");
}
System.out.println(msg.length());
channel.writeAndFlush(msg);

服务端代码:

public void initChannel(SocketChannel ch) throws Exception {
   ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
   ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(4));
   ch.pipeline().addLast("decoder", new StringDecoder());
   ch.pipeline().addLast("encoder", new StringEncoder());
   ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
      @Override
      public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.err.println("server:" + msg.toString());
        ctx.writeAndFlush(msg.toString() + "你好");
      }
   });
}

客户端,直接发送就行

ChannelFuture f = b.connect("127.0.0.1", 2222).sync();
channel = f.channel();![](https://s4.51cto.com/images/blog/202008/04/fb05cdb6bd8458bd1006a127ff9d12dc.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
StringBuilder msg = new StringBuilder();
for (int i = 0; i < 100; i++) {
  msg.append("hello yinjihuan");
}
channel.writeAndFlush(msg);

源码参考:https://github.com/yinjihuan/netty-im

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • SpringBoot+WebSocket+Netty实现消息推送的示例代码

    上一篇文章讲了Netty的理论基础,这一篇讲一下Netty在项目中的应用场景之一:消息推送功能,可以满足给所有用户推送,也可以满足给指定某一个用户推送消息,创建的是SpringBoot项目,后台服务端使用Netty技术,前端页面使用WebSocket技术. 大概实现思路: 前端使用webSocket与服务端创建连接的时候,将用户ID传给服务端 服务端将用户ID与channel关联起来存储,同时将channel放入到channel组中 如果需要给所有用户发送消息,直接执行channel组的writ

  • 在SpringBoot中整合使用Netty框架的详细教程

    Netty是一个非常优秀的Socket框架.如果需要在SpringBoot开发的app中,提供Socket服务,那么Netty是不错的选择. Netty与SpringBoot的整合,我想无非就是要整合几个地方 让netty跟springboot生命周期保持一致,同生共死 让netty能用上ioc中的Bean 让netty能读取到全局的配置 整合Netty,提供WebSocket服务 这里演示一个案例,在SpringBoot中使用Netty提供一个Websocket服务. servlet容器本身提

  • SpringBoot整合Netty心跳机制过程详解

    前言 Netty 是一个高性能的 NIO 网络框架,本文基于 SpringBoot 以常见的心跳机制来认识 Netty. 最终能达到的效果: 客户端每隔 N 秒检测是否需要发送心跳. 服务端也每隔 N 秒检测是否需要发送心跳. 服务端可以主动 push 消息到客户端. 基于 SpringBoot 监控,可以查看实时连接以及各种应用信息. IdleStateHandler Netty 可以使用 IdleStateHandler 来实现连接管理,当连接空闲时间太长(没有发送.接收消息)时则会触发一个

  • 深入浅析Netty 在 Dubbo 中是如何应用的

    众所周知,国内知名框架 Dubbo 底层使用的是 Netty作为网络通信,那么内部到底是如何使用的呢?今天我们就来一探究竟. 1. dubbo 的 Consumer 消费者如何使用 Netty 注意:此次代码使用了从 github 上 clone 的 dubbo 源码中的 dubbo-demo 例子. 代码如下: System.setProperty("java.net.preferIPv4Stack", "true"); ClassPathXmlApplicati

  • 利用Java搭建个简单的Netty通信实例教程

    前言 看过dubbo源码的同学应该都清楚,使用dubbo协议的底层通信是使用的netty进行交互,而最近看了dubbo的Netty部分后,自己写了个简单的Netty通信例子. 准备 工程截图 模块详解 rpc-common rpc-common作为各个模块都需使用的模块,工程中出现的是一些通信时请求的参数以及返回的参数,还有一些序列化的工具. rpc-client rpc-client中目前只是单单的一个NettyClient启动类. rpc-server rpc-client中目前也只是单单的

  • Netty与Spring Boot的整合的实现

    ​ 最近有朋友向我询问一些Netty与SpringBoot整合的相关问题,这里,我就总结了一下基本整合流程,也就是说,这篇文章 ,默认大家是对netty与Spring,SpringMVC的整合是没有什么问题的.现在,就进入正题吧. Server端: 总的来说,服务端还是比较简单的,自己一共写了三个核心类.分别是 NettyServerListener:服务启动监听器 ServerChannelHandlerAdapter:通道适配器,主要用于多线程共享 RequestDispatcher:请求分

  • spring boot整合netty的实现方法

    之前花了几天去研究怎么使用netty做一个网关服务器,虽然最后还是没能用上我做的网关,但是呢netty是会用了,总结一下netty和spring boot整合.感觉不用spring boot都不会写代码了.哈哈哈 在pom文件中添加相关的依赖,这里主要的就是netty的依赖,spring boot的相关依赖本文不提 <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artif

  • Netty粘包拆包问题解决方案

    TCP黏包拆包 TCP是一个流协议,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 怎么解决? • 消息定长度,传输的数据大小固定长度,例如每段的长度固定为100字节,如果不够空位补空格 • 在数据包尾部添加特殊分隔符,比如下划线,中划线等 • 将消息分为消息头和

  • Netty粘包拆包及使用原理详解

    目录 为什么使用Netty框架 Netty框架介绍 Netty实战 Netty编写服务器端 Netty客户端 粘包与拆包 为什么使用Netty框架 NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector.ServerSocketChannel.SocketChannel.ByteBuffer等. 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程.这是因为NIO编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序. 可靠性能力补齐,工

  • Netty解决 TCP 粘包拆包的方法

    什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在.处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要. 我们都知道TCP是基于字节流的传输协议. 那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么意思什么地方有句

  • Golang TCP粘包拆包问题的解决方法

    什么是粘包问题 最近在使用Golang编写Socket层,发现有时候接收端会一次读到多个数据包的问题.于是通过查阅资料,发现这个就是传说中的TCP粘包问题.下面通过编写代码来重现这个问题: 服务端代码 server/main.go func main() { l, err := net.Listen("tcp", ":4044") if err != nil { panic(err) } fmt.Println("listen to 4044")

  • 使用Netty解决TCP粘包和拆包问题过程详解

    前言 上一篇我们介绍了如果使用Netty来开发一个简单的服务端和客户端,接下来我们来讨论如何使用解码器来解决TCP的粘包和拆包问题 TCP为什么会粘包/拆包 我们知道,TCP是以一种流的方式来进行网络转播的,当tcp三次握手简历通信后,客户端服务端之间就建立了一种通讯管道,我们可以想象成自来水管道,流出来的水是连城一片的,是没有分界线的. TCP底层并不了解上层的业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分. 所以对于我们应用层而言.我们直观是发送一个个连续完整TCP数据包的,

  • go语言处理TCP拆包/粘包的具体实现

    目录 part 1 part 2 part 3 part 1 最近在学习go自带的rpc,看完了一遍想着自己实现一个codec,也就是自定义消息的序列化和反序列化.消息的序列化和反序列化涉及到两步: 1.从网络中读取数据和将数据写到网络中: 2.根据拿到的二进制数据反序列化以及把现有的对象序列化成二进制数据.而这个过程中就需要处理TCP的拆包粘包了. TCP的拆包/粘包也算是网络编程中一个比较基础的问题了,具体的问题含义和解决方式也不再详细描述了.虽然作为实现应用层逻辑的程序员可能根本不需要关心

  • 详解python中TCP协议中的粘包问题

    TCP协议中的粘包问题 1.粘包现象 基于TCP实现一个简易远程cmd功能 #服务端 import socket import subprocess sever = socket.socket() sever.bind(('127.0.0.1', 33521)) sever.listen() while True: client, address = sever.accept() while True: try: cmd = client.recv(1024).decode('utf-8') p

  • golang网络socket粘包问题的解决方法

    本文实例讲述了golang网络socket粘包问题的解决方法.分享给大家供大家参考,具体如下: 看到很多人问这个问题, 今天就写了个例子, 希望能帮助大家 首先说一下什么是粘包:百度上比较通俗的说法是指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾. 解决方案如下: 服务端: 复制代码 代码如下: package main import (     "bytes"     "encoding/binary&quo

  • C#中TCP粘包问题的解决方法

    一.TCP粘包产生的原理 1.TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾.出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成. 2.发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据.若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据.接收方引起的粘包是由于接收方用户进程不及时接收数据,从

  • python socket网络编程之粘包问题详解

    一,粘包问题详情 1,只有TCP有粘包现象,UDP永远不会粘包 你的程序实际上无权直接操作网卡的,你操作网卡都是通过操作系统给用户程序暴露出来的接口,那每次你的程序要给远程发数据时,其实是先把数据从用户态copy到内核态,这样的操作是耗资源和时间的,频繁的在内核态和用户态之前交换数据势必会导致发送效率降低, 因此socket 为提高传输效率,发送方往往要收集到足够多的数据后才发送一次数据给对方.若连续几次需要send的数据都很少,通常TCP socket 会根据优化算法把这些数据合成一个TCP段

随机推荐