netty学习

Netty

Netty认识

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

线程模型

在这里插入图片描述
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) 添加到链中的最后一个位置

在这里插入图片描述
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) 该方法用于客户端, 用来连接服务器端

Sync

bootstrap.connect().sync(); //直到连接返回,才会退出当前线程
 
future.channel().closeFuture().sync() //直到channel关闭,才会退出当前线程

断开连接

ChannelHandlerContext.close();

Netty案例实现

目标: 使用netty客户端给服务端发送数据,服务端接收消息打印.
首先引入Maven依赖

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

然后,下面是服务端实现部分

package com.lagou.Netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
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.创建 NioEventLoopGroup的两个实例:bossGroup workerGroup
        // 当前这两个实例代表两个线程池,默认线程数为CPU核心数乘2
        // bossGroup接收客户端传过来的请求
        // workerGroup处理请求
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        //2、创建服务启动辅助类:组装一些必要的组件
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //设置组,第一个bossGroup负责连接, workerGroup负责连接之后的io处理
        serverBootstrap.group(bossGroup,workerGroup)
                //channel方法指定服务器监听的通道类型
                .channel(NioServerSocketChannel.class)
                //设置channel handler , 每一个客户端连接后,给定一个监听器进行处理
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        //传输通道
                        ChannelPipeline pipeline = ch.pipeline();
                        //在通道上添加对通道的处理器 , 该处理器可能还是一个监听器
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        //监听器队列上添加我们自己的处理方式..
                        pipeline.addLast(new SimpleChannelInboundHandler<String>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
                                System.out.println(s);
                            }
                        });
                    }
                });
        //bind监听端口
        ChannelFuture f = serverBootstrap.bind(8000).sync();
        System.out.println("tcp server start success..");
        //会阻塞等待直到服务器的channel关闭
        f.channel().closeFuture().sync();
	}	
}

客户端NIO的实现部分
NettyClient.java

public class NettyClient {
	public static void main(String[] args) throws InterruptedException {
	public static void main(String[] args) throws InterruptedException {
        //客户端的启动辅助类
        Bootstrap bootstrap = new Bootstrap();
        //线程池的实例
        NioEventLoopGroup group = new NioEventLoopGroup();
        //添加到组中
        bootstrap.group(group)
                //channel方法指定通道类型
                .channel(NioSocketChannel.class)
                //通道初始化了
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                });
                
        Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
        while (true) {
           channel.writeAndFlush(new Date() + ": hello world!");
           Thread.sleep(2000); 
           }
    }
}

连接多个服务端

如果你现在需要连接100个服务器,你会怎么做呢?

for(Host host : hosts){
          //创建一个EventLoopGroup,可以简单认为是Netty框架下的线程池,默认线程数量是处理器数量的2倍
          EventLoopGroup group = new NioEventLoopGroup(1);
          try {
        //Netty建立连接的辅助类
              Bootstrap b = new Bootstrap();
        //配置属性,向pipeline添加handler
              b.group(group)
               .channel(NioSocketChannel.class)
               .option(ChannelOption.TCP_NODELAY, true)
               .handler(new ChannelInitializer<SocketChannel>() {
                   @Override
                   public void initChannel(SocketChannel ch) throws Exception {
                       ChannelPipeline p = ch.pipeline();
                       if (sslCtx != null) {
                           p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                       }
                       //p.addLast(new LoggingHandler(LogLevel.INFO));
                       p.addLast(new EchoClientHandler());
                   }
               });
   
              //启动建立连接
              ChannelFuture f = b.connect(HOST, PORT).sync();
   
              //block直到连接被关闭
              f.channel().closeFuture().sync();
}

问题很明显,如果每一个channel都对应一个NIOEventLoopGroup,那么我们实际上构建了一个connection:thread = 1:1的模型,随着连接数不断地扩大,线程膨胀的问题就会突显出来。

那么如何避免线程膨胀的问题呢?很简单,我们只要稍微修改下上面的代码就可以了。

NioEventLoopGroup group = new NioEventLoopGroup();
    Bootstrap b = new Bootstrap();
 
    try {
      b.group(group)
       .channel(NioSocketChannel.class)
       .option(ChannelOption.TCP_NODELAY, true)
       .handler(new ChannelInitializer<SocketChannel>() {
           @Override
           public void initChannel(SocketChannel ch) throws Exception {
               ChannelPipeline p = ch.pipeline();
               if (sslCtx != null) {
                   p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
               }
               //p.addLast(new LoggingHandler(LogLevel.INFO));
                           p.addLast(new EchoClientHandler());
                       }
         });
     for(Host HOST : Hosts){
         ChannelFuture f = b.connect(HOST, PORT).sync();
     }

如果我们需要独立的handler的话,我们需要创建多个Bootstrap。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值