Netty 编解码机制

  1. TCP 拆包和黏包
  2. 黏包
  3. 思考,UDP 会黏包吗?
  4. 拆包方案
  5. Netty 提供的拆包方案
  6. 自定义编码器

TCP 拆包和黏包

拆包黏包怎么来的呢?

为提升传输效率,每次要把固定长度数据包放满才发出去,所以不同的信息会被塞到一起

好比做缆车,一次要坐满 5 个人才能发车,但是你的团队只有 3 个人,所以这趟车有 2 个陌生人,下车后不能结伴只能 say88。那么,我们改如何找出这 2 个陌生人呢?这就是经典的拆包黏包问题!

黏包

我们现在编写代码模拟下黏包的情况

package com.shar.netty.netty.codec;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.junit.Before;
import org.junit.Test;

import java.nio.charset.Charset;

public class CodeTest {

    private ServerBootstrap bootstrap;

    @Before
    public void init() {
        bootstrap = new ServerBootstrap();
        bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(8));
        bootstrap.channel(NioServerSocketChannel.class);
    }

    @Test
    public void start() throws InterruptedException {
        bootstrap.childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel ch) {
                ch.pipeline().addLast(new TrackHandler());
            }
        });
        ChannelFuture future = bootstrap.bind(8080).sync();
        future.channel().closeFuture().sync();
    }

    public class TrackHandler extends SimpleChannelInboundHandler {
        int count = 0;
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf = (ByteBuf) msg;
            String message = buf.toString(Charset.defaultCharset());
            System.out.println(String.format("消息%s:%s", ++count, message));
        }
    }
}

运行上面的代码后,我便在本地电脑模拟出了一个 TCP 服务端

现在我打开 cmd ,输入 telnet localhost 8080 成功连接到服务端

然后,我在控制台依次输入1、2、3、4

此时,服务器会输出如下内容:

这说明:

  • 我的服务器正常工作 ~
  • 流式接收的工作模式

重点来了!我们在此处打个断点,然后以 debug 模式运行代码!

然后,我在控制台依次输入1、2、3、4、5、6,此时程序进入断点,我们放开断点调试,控制台发生了什么?

发生黏包的原理很简单

因为是流式工作模式,当你输入1后,程序进入断点程序阻塞,后续接收到的数据进入缓冲区等待,当我放掉第一个断点程序后,第二次进入时,程序一次性从缓冲区取到23456,这便是发送了黏包(这是站在客户端发送者角度说的,发送者希望是一个数字运行一次后台业务逻辑

思考,UDP 会黏包吗?

不会

拆包方案

实际业务场景中,黏包是不能被容忍的,那么如何解决呢?

  • 固定包长
  • 字符分割 (换行符、回车符什么的)
  • 请求头,标识大小(http、websocket、dubbo)

Netty 提供的拆包方案

Netty 里已经封装了拆包的实现类,在 initChannel 方法中,通过 addLast 启用

// 固定长度
ch.pipeline().addLast(new FixedLengthFrameDecoder(5));

// 换行,如果一行超过1024会报错
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

// 特殊字符
ByteBuf delimiteBuffer = Unpooled.wrappedBuffer(new byte[]{'$'});
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, true, delimiteBuffer));

// 处理具体业务
ch.pipeline().addLast(new TrackHandler());

自定义编码器

有的时候业务复杂,Netty 自带的拆包不能满足业务需求,我们也可以自定义编码器

public class MyFrameDecoder extends ByteToMessageCodec<String> {

    //编码
    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        // ...
    }

    //解码
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // ...
    }

}

然后通过 addLast 启用

ch.pipeline().addLast(new MyFrameDecoder());

转载请注明来源。 欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。 可以在下面评论区评论,也可以邮件至 sharlot2050@foxmail.com。

文章标题:Netty 编解码机制

字数:836

本文作者:夏来风

发布时间:2020-07-26, 11:07:58

原始链接:http://www.demo1024.com/blog/netty-codec/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。