Netty


##Netty
Netty 是由 JBOSS 提供一个异步的、 基于事件驱动的网络编程框架。
Netty 可以帮助你快速、 简单的开发出一 个网络应用， 相当于简化和流程化了 NIO 的开发过程。 作为当前最流行
的 NIO 框架， Netty 在互联网领域、 大数据分布式计算领域、 游戏行业、 通信行业等获得了广泛的应用， 知名的
Elasticsearch 、 Dubbo 框架内部都采用了 Netty。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783/5ea18443-bc47-3ef3-3405-86f767965efe.png)

##为什么使用Netty
###NIO缺点
NIO 的类库和 API 繁杂，使用麻烦。你需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、
ByteBuffer 等.
可靠性不强，开发工作量和难度都非常大
NIO 的 Bug。例如 Epoll Bug，它会导致 Selector 空轮询，最终导致 CPU 100%。
###Netty优点
对各种传输协议提供统一的 API
高度可定制的线程模型——单线程、一个或多个线程池
更好的吞吐量，更低的等待延迟
更少的资源消耗
最小化不必要的内存拷贝

##线程模型
###单线程模型
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783/c89e054d-96ea-d089-0f35-d93fd0c2d7c0.png)

###线程池模型
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783/8b34f9eb-fd63-378c-5adb-977455a5dc59.png)

###Netty 模型
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783/1dccbaf6-b7e9-cf1b-249d-ac2040f98137.png)
Netty 抽象出两组线程池， BossGroup 专门负责接收客 户端连接， WorkerGroup 专门负责网络读写操作。
NioEventLoop 表示一个不断循环执行处理 任务的线程， 每个 NioEventLoop 都有一个 selector， 用于监听绑定在其
上的 socket 网络通道。 NioEventLoop 内部采用串行化设计， 从消息的读取->解码->处理->编码->发送， 始终由 IO线 程 NioEventLoop 负责。

##Netty核心组件
###ChannelHandler 及其实现类
ChannelHandler 接口定义了许多事件处理的方法， 我们可以通过重写这些方法去实现具 体的业务逻辑
我们经常需要自定义一个 Handler 类去继承 ChannelInboundHandlerAdapter， 然后通过 重写相应方法实现业务逻
辑， 我们接下来看看一般都需要重写哪些方法：

```
- public void channelActive(ChannelHandlerContext ctx)， 通道就绪事件
- public void channelRead(ChannelHandlerContext ctx, Object msg)， 通道读取数据事件
- public void channelReadComplete(ChannelHandlerContext ctx) ， 数据读取完毕事件
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)， 通道发生异常事件
```
###ChannelPipeline
ChannelPipeline 是一个 Handler 的集合， 它负责处理和拦截 inbound 或者 outbound 的事 件和操作， 相当于一个
贯穿 Netty 的链。

```
- ChannelPipeline addFirst(ChannelHandler... handlers)， 把一个业务处理类（handler） 添加到链中的第一个位置
- ChannelPipeline addLast(ChannelHandler... handlers)， 把一个业务处理类（handler） 添加到链中的最后一个位置
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3783/f6dba1cb-5963-46f0-d517-0f7cde9d4c3a.png)

###ChannelHandlerContext
这 是 事 件 处 理 器 上 下 文 对 象 ， Pipeline 链 中 的 实 际 处 理 节 点 。 每 个 处 理 节 点ChannelHandlerContext
中 包 含 一 个 具 体 的 事 件 处 理 器 ChannelHandler ， 同 时 ChannelHandlerContext 中也绑定了对应的 pipeline
和 Channel 的信息，方便对 ChannelHandler 进行调用。 常用方法如下所示

```
- ChannelFuture close()， 关闭通道
- ChannelOutboundInvoker flush()， 刷新
- ChannelFuture writeAndFlush(Object msg) ， 将 数 据 写 到 ChannelPipeline 中 当 前
- ChannelHandler 的下一个 ChannelHandler 开始处理（出站）
```
###ChannelFuture
表示 Channel 中异步 I/O 操作的结果， 在 Netty 中所有的 I/O 操作都是异步的， I/O 的调 用会直接返回， 调用者并
不能立刻获得结果， 但是可以通过 ChannelFuture 来获取 I/O 操作 的处理状态。 常用方法如下所示：

```
Channel channel()， 返回当前正在进行 IO 操作的通道
ChannelFuture sync()， 等待异步操作执行完毕
```
###EventLoopGroup 和其实现类 NioEventLoopGroup
EventLoopGroup 是一组 EventLoop 的抽象， Netty 为了更好的利用多核 CPU 资源， 一般 会有多个 EventLoop 同时
工作， 每个 EventLoop 维护着一个 Selector 实例。 EventLoopGroup 提供 next 接口， 可以从组里面按照一定规则
获取其中一个 EventLoop 来处理任务。 在 Netty 服务器端编程中， 我们一般都需要提供两个 EventLoopGroup， 例
如： BossEventLoopGroup 和 WorkerEventLoopGroup。

```
- public NioEventLoopGroup()， 构造方法
- public Future<?> shutdownGracefully()， 断开连接， 关闭线程
```

###ServerBootstrap 和 Bootstrap
ServerBootstrap 是 Netty 中的服务器端启动助手，通过它可以完成服务器端的各种配置； Bootstrap 是 Netty 中的客
户端启动助手， 通过它可以完成客户端的各种配置。 常用方法如下 所示：

```
- public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)，
  该方法用于服务器端， 用来设置两个 EventLoop
- public B group(EventLoopGroup group) ， 该方法用于客户端， 用来设置一个 EventLoop
- public B channel(Class<? extends C> channelClass)， 该方法用来设置一个服务器端的通道实现
- public <T> B option(ChannelOption<T> option, T value)， 用来给 ServerChannel 添加配置
- public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value)， 用来给接收到的通道添加配置
- public ServerBootstrap childHandler(ChannelHandler childHandler)， 该方法用来设置业务处理类（自定义的 handler）
- public ChannelFuture bind(int inetPort) ， 该方法用于服务器端， 用来设置占用的端口号
- public ChannelFuture connect(String inetHost, int inetPort) 该方法用于客户端， 用来连接服务器端
```


##Netty版案例实现
```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>netty</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.6.Final</version>
        </dependency>
    </dependencies>

</project>
```

```NettyServer.java
package com.lagou.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.sctp.nio.NioSctpServerChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

// 接收客户端请求，打印在控制台
public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //1.创建2个线程池对象
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        //2.创建启动引导类
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        //3.设置启动引导类
        //添加到组中，两个线程池，第一个位置的线程池就负责接收，第二个参数就负责读写
        serverBootstrap.group(bossGroup,workGroup)
                //给我们当前设置一个通道类型
                .channel(NioServerSocketChannel.class)
                //绑定一个初始化监听
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    //事件监听Channel通道
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        //获取pipeLine
                        ChannelPipeline pipeline = nioSocketChannel.pipeline();
                        //绑定编码
                        pipeline.addFirst(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        //绑定我们的业务逻辑
                        pipeline.addLast(new SimpleChannelInboundHandler<String>() {
                            protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
                                //获取入栈信息，打印客户端传递的数据
                                System.out.println(msg);
                            }
                        });
                    }
                });

        //4.启动引导类绑定端口
        ChannelFuture future = serverBootstrap.bind(9999).sync();

        //5.关闭通道
        future.channel().closeFuture().sync();
    }
}
```

```NettyClient.java
package com.lagou.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;

//客户端给服务器发送数据
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        //1.创建连接池对象
        NioEventLoopGroup group = new NioEventLoopGroup();

        //2.创建客户端的启动引导类 BootStrap
        Bootstrap bootstrap = new Bootstrap();

        //3.配置启动引导类
        bootstrap.group(group)
            //设置通道为Nio
            .channel(NioSocketChannel.class)
            //设置Channel初始化监听器
            .handler(new ChannelInitializer<Channel>() {
                @Override
                protected void initChannel(Channel channel) throws Exception {
                    //设置编码
                    channel.pipeline().addLast(new StringEncoder());
                }
            });

        //4.适用启动引导类连接服务器， 获取一个channel
        Channel channel = bootstrap.connect("127.0.0.1", 9999).channel();

        //5.循环写数据给服务器
        while (true){
            channel.writeAndFlush("hello server .. this is client ...");
            Thread.sleep(2000);
        }
    }
}
```