netty 基础补充

229 阅读5分钟

image.png

1. 基础概念

1.1 阻塞和非阻塞

在访问数据的时候,数据没有准备好时的处理方式;

  • 阻塞 : 等待数据处理好后才开始处理其他的事情;
  • 非阻塞 : 直接返回,去处理其他事情;

1.2 同步和异步

基于应用程序操作系统处理IO事件所采用的方式。比如:

  • 同步:应用程序直接参与IO读写的操作;
  • 异步:所有的IO读写都交给操作系统去处理,应用程序只需要等待通知;

1.3 BIO和NIO

IO模型BIONIO
通信面向流(乡村公路)面向缓冲(高速公路,多路复用技术)
处理阻塞IO(多线程)非阻塞IO(反应堆Reactor)
触发选择器(轮询机制)

2. IO模型

常见IO模型

  • 同步阻塞 I/O(BIO)
  • 同步非阻塞 I/O(NIO)

  • I/O 多路复用

  • 信号驱动 I/O  (【不怎么用】)
  • 异步 I/O (【不怎么用】)

Reactor线程模型

单线程模型

I/O 操作(包括连接建立、数据读写、事件分发等),都是由一个线程完成的;

缺点:

  • 连接数有限,CPU很容易打满;
  • 线程在处理I/O事件时,Select 无法建立连接,分发事件等操作;

多线程模型

\

  • Reactor 多线程模型将业务逻辑交给多个线程进行处理;
  • 多线程模型其他的操作与单线程模型是类似的,例如读取数据依然保留了串行化的设计。当客户端有数据发送至服务端时,Select 会监听到可读事件,数据读取完毕后提交到业务线程池中并发处理

主从模型

  • 主从多线程模型由多个 Reactor 线程组成,每个 Reactor 线程都有独立的 Selector 对象。
  • MainReactor 仅负责处理客户端连接的 Accept 事件,连接建立成功后将新创建的连接对象注册至 SubReactor。
  • SubReactor 分配线程池中的 I/O 线程与其连接绑定,它将负责连接生命周期内所有的 I/O 事件

3. 零拷贝

0拷贝是指,在数据操作的时候,不需要将数据从一个内存位置拷贝到另一个内存位置;
即数据操作,避免不必要的内存拷贝就是0拷贝;

常规拷贝

0拷贝

DMA :DMA是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。


4. TCP 粘包、拆包

4.1 问题由来

TCP传输面向流,数据包没有边界;客户端可能将一个大包拆分成小包进行发送,也可能将多个报文合并成一个大的报文发送。故产生了拆包粘包;

Nagle算法:1984 年被福特航空和通信公司定义为 TCP/IP 拥塞控制方法。它主要用于解决频繁发送小数据包而带来的网络拥塞问题;

Linux 提供的 TCP_NODELAY 参数禁用 Nagle 算法。Netty 中为了使数据传输延迟最小化,就默认禁用了 Nagle 算法,这一点与 Linux 操作系统的默认行为是相反的。

4.2 拆包/粘包的解决方案

消息长度固定

缺点:大小不好固定(server 和client 端大小需一致);

特定分隔符

缺点:内容中有相似的字符,解析会有问题;

消息长度 + 消息内容

最常用协议之一;消息头还可以放置其他东西(协议、版本号、算法等)

4.3 自定义协议

通用协议设计

即通信双方约定好的暗语;市面上已有的通用通信协议:HTTP、HTTPS、FTP等;

通用的协议示例

+---------------------------------------------------------------+

| 魔数 2byte | 协议版本号 1byte | 序列化算法 1byte | 报文类型 1byte |

+---------------------------------------------------------------+

| 状态 1byte |    保留字段 4byte   |   数据长度 4byte   | 

+---------------------------------------------------------------+

|          数据内容 (长度不定)             |

+---------------------------------------------------------------+

Netty 如何实现自定义协议

Netty 常用编码器:

  • MessageToByteEncoder 对象编码成字节流;
  • MessageToMessageEncoder 一种消息类型编码成另外一种消息类型。

Netty常用解码器:

  • ByteToMessageDecoder/ReplayingDecoder 将字节流解码为消息对象;
  • MessageToMessageDecoder 将一种消息类型解码为另外一种消息类型。

编接码器分为1次编解码器和2次编解码器;

  • 一次解码器用于解决 TCP 拆包/粘包问题,按协议解析后得到的字节数据;
  • 二次解码器用于解决字节数据到对象模型之间的转化;
  • 一次编解码器:MessageToByteEncoder/ByteToMessageDecoder
  • 二次编解码器:MessageToMessageEncoder/MessageToMessageDecoder

Netty 支持哪些常用的解码器?

固定长度解码器

FixedLengthFrameDecoder

public class EchoServer {

    public void startEchoServer(int port) throws Exception {

        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) {

                            ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
                            ch.pipeline().addLast(new EchoServerHandler());
                        }

                    });
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();

        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
   

}

@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        System.out.println("Receive client : [" + ((ByteBuf) msg).toString(CharsetUtil.UTF_8) + "]");
    }
}

特殊分隔符解码器

DelimiterBasedFrameDecoder

b.group(bossGroup, workerGroup)

    .channel(NioServerSocketChannel.class)

    .childHandler(new ChannelInitializer<SocketChannel>() {

        @Override

        public void initChannel(SocketChannel ch) {

            ByteBuf delimiter = Unpooled.copiedBuffer("&".getBytes());

            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(10, true, true, delimiter));

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

        }

});

长度域解码器

长度域解码器 LengthFieldBasedFrameDecoder 是解决 TCP 拆包/粘包问题最常用的解码器。 它基本上可以覆盖大部分基于长度拆包场景,开源消息中间件 RocketMQ 就是使用 LengthFieldBasedFrameDecoder 进行解码的;

+---------------------------------------------------------------+

| 魔数 2byte | 协议版本号 1byte | 序列化算法 1byte | 报文类型 1byte  |

+---------------------------------------------------------------+

| 状态 1byte |        保留字段 4byte     |      数据长度 4byte     | 

+---------------------------------------------------------------+

|                   数据内容 (长度不定)                          |

+---------------------------------------------------------------+

参见

TCP粘包拆包及解决方法

Netty 支持哪些常用的解码器?