四、Netty简单案例实践及服务端启动流程

首先来看一个简单的Netty服务器和客户端通讯的例子

服务器代码如下:

public class SimpleNettyServer {
    public static void main(String[] args) {
        new SimpleNettyServer(8878).runServer();
    }

    private final int serverPort;
    ServerBootstrap serverBootstrap = new ServerBootstrap();

    public SimpleNettyServer(int serverPort) {
        this.serverPort = serverPort;
    }

    public void runServer() {
        //创建反应器线程组
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //1.设置反应器线程组
            serverBootstrap.group(bossGroup, workerGroup);
            //2.设置NIO类型的通道
            serverBootstrap.channel(NioServerSocketChannel.class);
            //3.设置通道的参数
            serverBootstrap.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
            serverBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //4.装配子通道流水线
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    //流水线管理子通道中的Handler处理器
                    //向子通道流水线中添加一个Handler处理器
                    ch.pipeline().addLast(new SimpleNettyServerHandler());
                }
            });
            //5.开始绑定通道
            //通过调用sync同步方法阻塞直到绑定成功
            ChannelFuture channelFuture = serverBootstrap.bind(serverPort).sync();
            channelFuture.addListener((ChannelFutureListener) future -> {
                if (channelFuture.isSuccess()) {
                    System.out.println("监听端口" + serverPort + " 成功");
                } else {
                    System.out.println("监听端口" + serverPort + "失败");
                }
            });
            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

服务端Handler代码如下:

public class SimpleNettyServerHandler extends ChannelInboundHandlerAdapter {

    //收到数据读取
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buf= (ByteBuf) msg;
            System.out.println("收到消息"+buf.toString(CharsetUtil.UTF_8));
    }

    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        //writeAndFlush 是 write + flush
        //将数据写入到缓存,并刷新
        ctx.writeAndFlush(Unpooled.copiedBuffer("客户端消息已收到并处理", CharsetUtil.UTF_8));
    }
}

客户端代码:

public class SimpleNettyClient {

    public static void main(String[] args) {
        new SimpleNettyClient("127.0.0.1",8878).startClient();

    }
    EventLoopGroup group = new NioEventLoopGroup();
    private final int serverPort;
    private final String serverAddress;

    public SimpleNettyClient(String  serverAddress, int serverPort ) {
        this.serverAddress=serverAddress;
        this.serverPort = serverPort;
    }

    public void startClient() {
        try {
            //1.创建客户端启动对象
            Bootstrap bootstrap = new Bootstrap();
            //2.设置相关参数
            bootstrap.group(group) //设置线程组
                    .channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new SimpleNettyClientHandler()); //加入自己的处理器
                        }
                    });
            //3.启动客户端去连接服务器端
            ChannelFuture channelFuture = bootstrap.connect(serverAddress, serverPort).sync();
            channelFuture.addListener((ChannelFutureListener)future->{
                if (channelFuture.isSuccess()) {
                    System.out.println("连接服务器成功");
                    System.out.println("客户端 ok..");
                } else {
                    System.out.println("连接服务器失败");
                }
            });
            //给关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();

        }
    }
}

客户端Handler代码:

public class SimpleNettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        String  msg="hello, server。This is client。";
        System.out.println("发往服务端的消息为"+msg);
        ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
    }

    //当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
        System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

下面我们结合上面的代码来简单分析下Netty的启动流程:

一、NioEventLoopGroup

          在服务端启动时,我们创建了两个NioEventLoopGroup对象,这两个对象可以看做是传统IO编程模型的两大线程组,bossGroup

表示监听端口,accept 新连接的线程组,workerGroup表示处理每一条连接的数据读写的线程组。

先看一下NioEventLoopGroup初始化的过程:

根据debug进入初始化流程可以看到最终的调用的是其父类MultithreadEventExecutorGroup的初始化方法

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) 

参数解析:

在父类的构造函数中我们可以看到这些参数是怎么初始化的。

1. nThreads是指定的,如果没有填的话默认是cup处理器核心数*2(静态代码块中初始化)。

2. executor此处为null,在方法体中将实例化。

3. chooserFactory指定为DefaultEventExecutorChooserFactory.INSTANCE,在静态属性中可以看到为                                                    new DefaultEventExecutorChooserFactory(),主要是用于选择下一个可用的EventExecutor即NioEventLoop。

4. args为传递给创建NioEventLoop的newChild方法的调用的参数

* @param args   arguments which will passed to each {@link #newChild(Executor, Object...)} call

  4.1   SelectorProvider.provider();-->SelectorProvider.open()返回一个Selector。

  4.2   DefaultSelectStrategyFactory.INSTANCE 默认选择策略工厂

  4.3   RejectedExecutionHandlers.reject();拒绝策略处理器

 

构造函数解析:

1.初始化executor,创建了一个线程创建器。

  if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

在此方法中有以下代码:

@Override
    public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }

当有任务传进来的时候,都将创建一个线程实体去处理这个任务。并将传进来的可执行任务包装为FastThreadLocalThread

protected Thread newThread(Runnable r, String name) {
        return new FastThreadLocalThread(threadGroup, r, name);
    }

2. 初始化children -->创建一个 EventExecutor[nThreads]数组,用来保存NioEventLoop,接下来是循环初始化NioEventLoop,指定的参数为上方的参数args...。

3.线程选择器,主要是为新连接绑定对应的NioEventLoop,选择NioEventLoop的方法是chooser的next(),就是 Netty 需要一个 NioEventLoop 时, 会调用EventExecutorChooser的next() 方法获取一个可用的 EventLoop。

chooser = chooserFactory.newChooser(children);

根据方法isPowerOfTwo来判断对应的是哪个选择器。

此处executors.length为12,根据   isPowerOfTwo(executors.length)(是否为2的幂),此处返回的为GenericEventExecutorChooser选择器。

  public EventExecutorChooser newChooser(EventExecutor[] executors) {
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTowEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

下面是DefaultEventExecutorChooserFactory内部的两种事件选择器(主要是做循环选择,二进制的与运算明显效率要高)。代码如下:

  private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        PowerOfTowEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }

    private static final class GenericEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        GenericEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

        @Override
        public EventExecutor next() {
            return executors[Math.abs(idx.getAndIncrement() % executors.length)];
        }
    }

完成实例化的BossGroup Debug截图如下:

       可以看到内部存储了一个EventExecutor数组,其中的每个NioEventLoop相当于一个子反应器。Netty的NioEventLoopGroup就相当于一个多线程版的反应器。在之前的Reactor反应器模式中,一般使用两个反应器,一个负责新连接的监听和接受,一个负责IO事件处理。对应到Netty服务器程序中则是设置两个NioEventLoopGroup线程组,一个EventLoopGroup负责监听和接受,一个负责IO的事件处理。

 

二、NIOEventLoop

先看一下Debug看到的其中一个NioEventLoop:

 

NIOEventLoop类图:

NioEventLoop是在Group创建时通过children[i] = newChild(executor, args);方法创建的。

 @Override
    protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider) args[0],
            ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
    }

这三个参数在上面已有说明,看一下创建过程:

1.  通过SelectorProvider去创建Selector。SelectorProvider是Java NIO中的抽象类,它的作用是调用Windows或者Linux底层NIO的实现,为JavaNIO提供服务,比如经常用的Selector.open()方法内部就是通过调用SelectorProvider.openSelector()来得到多路复用器selector并包装。一个Selector和一个NioEventLoop绑定。

provider = selectorProvider;
selector = openSelector();

2.  创建tailTasks = newTaskQueue(maxPendingTasks);此处maxPendingTasks(最大待处理任务)值为Integer.MAX_VALUE。

protected static final int DEFAULT_MAX_PENDING_TASKS = Math.max(16,
            SystemPropertyUtil.getInt("io.netty.eventLoop.maxPendingTasks", Integer.MAX_VALUE));

返回的是一个PlatformDependent.newMpscQueue(maxPendingTasks);

MpscQueue是针对Netty中NIO任务设计的一种队列,允许有多个生产者(外部线程),只有一个消费者(NioEventLoop)的队列。

3.  创建taskQueue = newTaskQueue(this.maxPendingTasks)

             --->PlatformDependent.newMpscQueue(maxPendingTasks)。

4.   保存线程执行器。在MultithreadEventExecutorGroup构造函数中执行new ThreadPerTaskExecutor(newDefaultThreadFactory())并传入了newChild方法中,最终也传入该构造函数。

this.executor = ObjectUtil.checkNotNull(executor, "executor");

并且在SingleThreadEventExecutor类中有一个属性private volatile Thread thread,它用来引用支撑该EventExecutor的线程,用来处理I/O事件和执行任务,叫支撑线程或者I/O线程均可,thread所引用的线程即来自executor。

5.指定rejectedExecutionHandler拒绝任务处理策略。

重要属性:

1. Thread线程类的成员:NioEventLoop 继承于 SingleThreadEventLoop, 而 SingleThreadEventLoop 又继承于 SingleThreadEventExecutor。SingleThreadEventExecutor 是 Netty 中对本地线程的抽象, 它内部有一个 Thread thread 属性, 存储了一个本地 Java 线程。因此我们可以认为, 一个 NioEventLoop 其实和一个特定的线程绑定,并且在其生命周期内, 绑定的线程都不会再改变。

2.NIO选择器:通过前面提供的 provider.openSelector()返回一个Selector

3.TaskQueue任务队列。

 

三、服务端启动流程

  • 服务端的Socket在哪里初始化?
  • 在哪里Accept 连接?

 

1.创建Channel

从入口方法blind(port)进入:

-------->bind(new InetSocketAddress(inetPort));

     --------->doBind(localAddress);

         -------------->initAndRegister();初始化和注册Channel,下面看一下源码:

 channel = channelFactory.newChannel();
  init(channel);
 

   channelFactory.newChannel();方法返回的为 clazz.newInstance();

此处的clazz为serverBootstrap.channel(NioServerSocketChannel.class)绑定的NioServerSocketChannel.class类。

看一下构造方法,

  public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

1.1  newSocket方法参数为静态属性SelectorProvider.provider()(Java NIO包下的),该方法返回的为一个ServerSocketChannel(NIO           包下)。

1.2  config = new NioServerSocketChannelConfig(this, javaChannel().socket());Tcp参数配置类

1.3  父类AbstractNioChannel构造函数配置非阻塞:ch.configureBlocking(false);

1.4  父类AbstractChannel构造函数:

  •     创建Channel的唯一标识:newId();
  •     newUnsafe():Tcp底层的读写操作类;
  •     新建ChannelPipeLine
 protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }

2.初始化服务端Channel

由init()入口方法进入,主要作用就是保存用户自定义的属性,通过这个属性创建一个连接器。

2.1  channel.config().setOptions(options)和channel.attr(key).set(e.getValue())

      此处设置的options和attrs都是在设置服务端通道的参数时候设置的。

2.2  设置currentChildOptions和currentChildAttrs属性值,这两个属性值也是在服务端装配子通道流水线时设置的参数。每次在处理新连接         时都会将该属性配置上去。

 synchronized (childOptions) {
            currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }

2.3  配置服务端的Handler

 p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }

2.4  添加连接器,服务端的pipeLine都会默认有一个ServerBootstrapAcceptor,主要目的就是为新连接分配NIO的一个线程。

 ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });


3.  注册Selector

由initAndRegister()中的register(channel)进入: 

ChannelFuture regFuture = config().group().register(channel);

3.1  先调用了MultithreadEventLoopGroup的register方法,利用chooser.next()方法返回EventLoop

3.2  调用SingleThreadEventLoop的register方法,传入一个DefaultChannelPromise,绑定了EventLoop和channel

3.3  实际调用的为AbstractChannel的register方法:

      1) AbstractChannel.this.eventLoop = eventLoop绑定线程

      2) register0()实际注册:

            1.  doRegister()NIO底层的事件,并绑定attachment为自身

selectionKey = javaChannel().register(eventLoop().selector, 0, this);

        2.主要做一些事件的回调,服务端pipeline,触发事件的回调。

// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
                // user may already fire events through the pipeline in the ChannelFutureListener.
                pipeline.invokeHandlerAddedIfNeeded();

     3)pipeline.fireChannelRegistered();把channel注册成功的事件传播到用户的代码里去。

 

4.服务端口的绑定

入口方法为AbstractBootstrap的doBind(final SocketAddress localAddress)方法:

4.1 调用 AbstractBootstrap的doBind0(regFuture, channel, localAddress, promise)方法

最终调用的为NioServerSocketChannel的doBind方法,即Java底层的blind方法,此处的javaChannel就是之前创建的Channel。

调用堆栈

doBind:126, NioServerSocketChannel (io.netty.channel.socket.nio)
bind:554, AbstractChannel$AbstractUnsafe (io.netty.channel)
bind:1258, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeBind:512, AbstractChannelHandlerContext (io.netty.channel)
bind:497, AbstractChannelHandlerContext (io.netty.channel)
bind:980, DefaultChannelPipeline (io.netty.channel)
bind:250, AbstractChannel (io.netty.channel)

源码:

 @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }

4.2端口绑定成功之后,会调用pipeline.fireChannelActive()方法。

 if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

调用堆栈为:

doBeginRead:412, AbstractNioChannel (io.netty.channel.nio)
doBeginRead:55, AbstractNioMessageChannel (io.netty.channel.nio)
beginRead:769, AbstractChannel$AbstractUnsafe (io.netty.channel)
read:1286, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeRead:704, AbstractChannelHandlerContext (io.netty.channel)
read:684, AbstractChannelHandlerContext (io.netty.channel)
read:1011, DefaultChannelPipeline (io.netty.channel)
read:280, AbstractChannel (io.netty.channel)
readIfIsAutoRead:1346, DefaultChannelPipeline$HeadContext (io.netty.channel)
channelActive:1324, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelActive:224, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelActive:210, AbstractChannelHandlerContext (io.netty.channel)
fireChannelActive:902, DefaultChannelPipeline (io.netty.channel)
run:565, AbstractChannel$AbstractUnsafe$2 (io.netty.channel)

可以看到最终调用的方法为AbstractNioChannel的doBeginRead方法:

 @Override
    protected void doBeginRead() throws Exception {
        // Channel.read() or ChannelHandlerContext.read() was called
        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        readPending = true;

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }

    可以看到为通道注册了readInterestOp事件。在NIOServerSocketChannel初始化时可以看到readInterestOp就是SelectionKey.OP_ACCEPT事件。

 public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
  protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
        super(parent, ch, readInterestOp);
    }

至此,服务端程序启动完成。

 

 

posted @ 2019-12-17 23:44  寻找艾伦  阅读(44)  评论(0编辑  收藏  举报