Netty4使用指南(一) 基本篇

Netty是基于NIO的一种高性能通信框架,它支持多种协议如http、https、websocket,预置了多种编解码协议,避免了java原生NIO的一些bug如空轮询问题等。并且由于它的API使用简单,开发门槛低,所以netty是目前主流的NIO框架。

Reactor模型

要了解Netty的线程模型,我们首先要对Reactor线程模型有所了解,因为netty模型正是基于它的一个实现,所谓Reactor模型是基于事件驱动开发的,它也叫IO多路复用统一监听事件。它核心理念就是,当监听到客户端的连接时就将连接交给合适的业务线程去处理,监听的线程就叫做reactor,根据reactor线程和业务线程的数量,可以划分出三种reactor模型。

  1. 单线程Reactor模型,所有的操作均由一个线程去处理,包括接受客户端的连接,处理IO操作,业务处理。这样的好处是简单,但是当处理成千上万个NIO链路时,性能上无法支撑,线程负载过重时,还会出现处理速度太慢,导致客户端连接超时。
  2. 多线程Reactor模型,由一个reactor线程接受客户端的连接,而客户端的IO及业务处理则从一个工作线程池中获取可用的线程进行处理。相比单线程模型,这种模式已经足以面对大多数情况了,但是如果需要对客户端的连接进行一些处理,如安全校验等,单个reactor线程还是可能会处理不过来。
  3. 主从Reactor线程模型,该模型将reactor分为两部分一个主reactor线程负责监听客户端连接请求并将连接请求交给合适的从reactor线程处理,多个从reactor线程负责处理客户端的连接请求,而具体IO操作,业务处理则从工作线程池获取线程处理。

Netty基于Reactor模型的实现

单线程Reactor模型
    EventLoopGroup worker=new NioEventLoopGroup(1);
    ServerBootstrap serverBootstrap=new ServerBootstrap();
    serverBootstrap.group(worker);

此处的group方法实际上将worker同时设置为了Boss线程和工作线程,worker的线程数量又被限制为1,所以符合了单线程Reactor模型

多线程Reactor模型
  	EventLoopGroup worker=new NioEventLoopGroup();
    ServerBootstrap serverBootstrap=new ServerBootstrap();
    serverBootstrap.group(worker);

这里创建的NioEventLoopGroup线程池没有限制数量,它的默认线程数是处理器核数*2,因此也符合多线程Reactor模型

主从Reactor模型
  	EventLoopGroup worker=new NioEventLoopGroup();
  	EventLoopGroup boss=new NioEventLoopGroup();
    ServerBootstrap serverBootstrap=new ServerBootstrap();
    serverBootstrap.group(boss,worker);

理论上来说这应该就是主从Reactor模型,但是网上的资料显示netty似乎做出了一些修改
以下引用自https://segmentfault.com/a/1190000007403873

Netty 的服务器端的 acceptor 阶段, 没有使用到多线程, 因此上面的 主从多线程模型 在 Netty 的服务器端是不存在的.
服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程, 因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时, 实际上是在一个线程中的, 所以对只有一个服务的应用来说, bossGroup 设置多个线程是没有什么作用的, 反而还会造成资源浪费.

根据https://www.cnblogs.com/lvyahui/p/9030232.html博客描述,需要绑定多个套接字才会创建多个监听线程,测试同时监听两个端口

			serverBootstrap.bind(8080).sync();
            serverBootstrap.bind(8022).sync();

测试结果:在idea打印线程快照信息,发现当只监听一个端口时,即使指定了创建4个线程,但实际还是只有一个监听线程,只有监听了多个端口时才会创建多个监听线程。

Netty的基本概念

  1. EventLoopGroup:它负责两个工作, 第一个是作为 IO 线程, 负责相应的 IO 操作; 第二个是作为任务线程, 执行 taskQueue 中的任务。
  2. ServerBootstrap :这是neety的启动引导类,可以配置线程组,channel类型,增加处理器等。
  3. ChannelPipeline:相当于Reactor模型中的请求处理器,我们可以在这里添加多个Channel处理器,这些处理器根据数据流向又可分为ChannelInboundHandler和ChannelOutboundHandler两种处理器,一个处理服务器收到的数据,一个处理服务器发送的数据。
  4. ByteBuf:缓冲区,netty通常使用它来进行读写操作,通过调用flip方法可以切换读写模式。

echo服务器

服务端

package FrameWork.com.nio.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledDirectByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.concurrent.DefaultThreadFactory;

/**
 * Created by forget on 2019/12/2.
 */
public class NettyServer {
     

    private EventLoopGroup worker;
    private EventLoopGroup boss;
    private ServerBootstrap serverBootstrap;

    private ChannelFuture future;

    public NettyServer() {
     
        this.worker=new NioEventLoopGroup();
        this.boss=new NioEventLoopGroup();
        this.serverBootstrap=new ServerBootstrap();
    }

    public void start(int port){
     
        try
        {
     
            this.serverBootstrap.group(this.boss,this.worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer() {
     
                        @Override
                        protected void initChannel(Channel channel) throws Exception {
     
                        //编码器,将字符串消息转为字节发送给客户端
                        pipeline.addLast(new StringEncoder());
                        //解码器,将收到的字节消息转为字符串交给下一个处理器
                        pipeline.addLast(new StringDecoder());
                        //业务处理器
                        pipeline.addLast(new HelloServeHandle());
                        }
                    });

           this.future = this.serverBootstrap.bind(port).sync();
            System.out.println("netty服务器启动");
           this.future.channel().closeFuture().sync();

        }
        catch (Exception e)
        {
     
            e.printStackTrace();
        }
        finally {
     
             this.boss.shutdownGracefully();
             this.worker.shutdownGracefully();
        }
    }
}
public class HelloServeHandle extends SimpleChannelInboundHandler<String>{
     
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String o) throws Exception {
     
        System.out.println("收到客户端消息:"+o);
        channelHandlerContext.writeAndFlush(o);
    }
}

客户端

package FrameWork.com.nio.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;


public class Client {
     
    private EventLoopGroup eventLoopGroup;
    private Bootstrap Bootstrap;
    private ChannelFuture channelFuture;

    public Client() {
     
        this.eventLoopGroup=new NioEventLoopGroup();
        this.Bootstrap=new Bootstrap();
    }

    public void start(){
     
        this.Bootstrap.group(this.eventLoopGroup)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer() {
     
                            @Override
                            protected void initChannel(Channel channel) throws Exception {
     
                                ChannelPipeline pipeline=channel.pipeline();
                                pipeline.addLast(new StringEncoder());
								pipeline.addLast(new StringDecoder());
                                pipeline.addLast(new HelloClientHandle());
                            }
                        });

        try {
     
            this.channelFuture=this.Bootstrap.connect("localhost",8080).sync();

            this.channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }

    }
}
public class HelloClientHandle extends SimpleChannelInboundHandler<String>{
     
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String o) throws Exception {
     
        System.out.println("收到消息:"+o);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
     
        ctx.writeAndFlush(new String("hello world"));
    }
}

以上的代码运行之后,当客户端首次连接到服务端时会触发客户端的channelActive方法,向服务器发送"hello world",此时客户端在写入时会先在自身的pipeline寻找第一个outboundhandle,在这里就是StringEncoder编码器,将字符串编码为字节消息,然后继续寻找下一个处理器,没有则发送到服务器。服务器接收到消息后,也是先寻找inboundhandle,这里则是StringDecoder解码器,将字节消息解码为字符串,交给下一个处理器,即HelloServeHandle,触发channelRead0方法,将消息回写给客户端,下面的流程也和客户端相同,进行字符串编码操作。

下面是测试结果:
Netty4使用指南(一) 基本篇_第1张图片
在这里插入图片描述

你可能感兴趣的:(Netty,基本)