Netty(引导 Bootstrapping)

版权声明:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_37598011/article/details/84001146

Bootstrap类

引导类的层次结构包括一个抽象的父类和两个具体的引导子类:(ctrl+shift+alt+u)

     服务器致力于使用一个父Channel来接受来自客户端的连接,并创建子Channel以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父Channel的Channel来用于所有的网络交互。两种应用程序类型之间通用的引导步骤由AbstractBootstrap处理,而特定于客户端或者服务器的引导步骤则分别由Bootstrap或ServerBootstrap处理。

AbstractBootstrap类的完整声明是:

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable

在这个签名中,子类型B是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法)。

public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel>

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> 

引导客户端和无连接协议

Bootstrap类被用于客户端或者使用了无连接协议的应用程序中。

名称

描述

Bootstrap group(EventLoopGroup) 设置用于处理Channel所有事件的EventLoopGroup

Bootstrap channel(Class<? extends C>)

Bootstrap channelFactory(ChannelFactory<? extends C>)

channel()方法指定了Channel的实现类。如果该实现类没提供默认的构造函数,可以通过调用channelFactory()方法来指定一个工厂类,它将会被bind()方法调用
Bootstrap localAddress(SocketAddres) 指定Channel应该绑定到的本地地址。如果没有指定,则将由操作系统创建一个随机的地址。或者,也可以通过bind()或者connect()方法指定localAddress
<T> Bootstrap option(ChannelOption<T> option,T value) 设置ChannelOption,其将被应用到每个新创建的Channel的ChannelConfig。这些选项将会通过bind()或者connect()方法设置到Channel,不管哪个先被调用。这个方法在Channel已经被创建后再调用将不会有任何的效果。支持的ChannelOption取决于使用的Channel类型。
<T> Bootstrap attr(Attribute<T> key, T value) 指定新创建的Channel的属性值。这些属性值是通过bind()或者connect()方法设置到Channel的,具体取决于谁最先被调用。这个方法在Channel被创建后将不会有任何的效果。
Bootstraphandler(ChannelHandler) 设置将被添加到ChannelPipeline以接收事件通知的ChannelHandler
Bootstrap clone() 创建一个当前Bootstrap的克隆,其具有和原始的Bootstrap相同的设置信息
Bootstrap remoteAddress(SocketAddress) 设置远程地址。或者,也可以通过connect()方法来指定它
ChannelFuture connect() 连接到远程节点并返回一个ChannelFuture,其将 会在连接操作完成后接收到通知
ChannelFuture bind() 绑定Channel并返回一个ChannelFuture,其将  会在绑定操作完成后接收到通知,在那之后必须调用Channel.connect()方法来建立连接

引导客户端

Bootstrap类负责为客户端和使用无连接协议的应用程序创建Channel

在引导的过程中,在调用bind()或者connect()方法之前,必须调用以下方法来设置所需的组件:

  • group();
  • channel()或者channelFactory();
  • handler()。

如果不这样做,则将会导致IllegalStateException。对handler()方法的调用尤其重要,因为它需要配置好ChannelPipeline。

不能混用具有不同前缀的组件,如NioEventLoopGroup和OioSocketChannel。

引导服务器

ServerBootstrap类

名称 描述
group 设置ServerBootstrap要用的EventLoopGroup。这个EventLoopGroup将用于ServerChannel和被接受的子Channel的I/O处理
channel 设置将要被实例化的ServerChannel类
channelFactory 如果不能通过默认的构造函数创建Channel,那么可以提供一个Channel-Factory
localAddress 指定ServerChannel应该绑定到的本地地址。如果没有指定,则将由操作系统使用一个随机地址。或者,可以通过bind()方法来指定该localAddress
option 指定要应用到新创建的ServerChannel的ChannelConfig的ChannelOption。这些选项将会通过bind()方法设置到Channel。在bind()方法被调用之后,设置或者改变ChannelOption都不会有任何的效果。所支持的ChannelOption取决于所使用的Channel类型。
childOption 指定当子Channel被接受时,应用到子Channel的ChannelConfig的ChannelOption。所支持的ChannelOption取决于所使用的Channel的类型。
attr 指定ServerChannel上的属性,属性将会通过bind()方法设置给Channel。在调用bind()方法之后改变它们将不会有任何的效果
childAttr 将属性设置给已经被接受的子Channel。接下来的调用将不会有任何的效果
handler 设置被添加到ServerChannel的ChannelPipeline中的ChannelHandler。更加常用的方法参见childHandler()
childHandler 设置将  被添加到 已被接受  的子Channel的ChannelPipeline中的Channel-Handler。handler()方法和childHandler()方法之间的区别是:前者所添加的ChannelHandler由接受子Channel的ServerChannel处理,而childHandler()方法所添加的ChannelHandler将由已被接受的子Channel处理,其代表一个绑定到远程节点的套接字
clone 克隆一个设置和原始的ServerBootstrap相同的ServerBootstrap
bind 绑定ServerChannel并且返回一个ChannelFuture,其将会在绑定操作完成后收到通知(带着成功或者失败的结果)

引导服务器

    childHandler()、childAttr()和childOption(),这些调用支持特别用于服务器应用程序的操作。具体来说,ServerChannel的实现负责创建子Channel,这些子Channel代表了已被接受的连接。因此,负责引导ServerChannel的ServerBootstrap提供了这些方法,以简化将设置应用到已被接受的子Channel的ChannelConfig的任务。

 从Channel引导客户端

    假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当一个应用程序(如一个代理服务器)必须要和组织现有的系统(如Web服务或者数据库)集成时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子Channel中引导一个客户端Channel。

    通过将已被接受的子Channel的EventLoop传递给Bootstrap的group()方法来共享该EventLoop。因为分配给EventLoop的所有Channel都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。

其核心是通过ChannelHandlerContext获取当前channel的eventLoop。

尽可能地重用EventLoop,以减少线程创建所带来的开销。

在引导过程中添加多个ChannelHandler

    只需要简单地向Bootstrap或ServerBootstrap的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel()版本。在该方法返回之后,ChannelInitializer的实例将会从ChannelPipeline中移除它自己。

使用Netty的ChannelOption和属性 

    可以使用option()方法来将ChannelOption应用到引导。你所提供的值将会被自动应用到引导所创建的所有Channel。可用的ChannelOption包括了底层连接的详细信息,如keep-alive或者超时属性以及缓冲区设置。

    像Channel这样的组件可能甚至会在正常的Netty生命周期之外被使用。在某些常用的属性和数据不可用时,Netty提供了AttributeMap抽象(一个由Channel和引导类提供的集合)以及AttributeKey<T>(一个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户端和服务器Channel(包含ServerChannel的子Channel)相关联了。

package netty.in.action;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import io.netty.util.ByteProcessor;
import io.netty.util.concurrent.ScheduledFuture;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class EchoClient {
    final ByteBuf buf = Unpooled.copiedBuffer("Hello,王宝强", Charset.forName("UTF-8"));
    final AttributeKey<Integer>  id = AttributeKey.newInstance("ID");//创建一个AttributeKey以标识该属性
    public void connect(int port, String host) throws Exception {
        // 配置客户端NIO线程组
                EventLoopGroup group = new NioEventLoopGroup();
        try {

            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    //设置ChannelOption,其将在connect()或者bind()方法被调用时被设置到已经创建的Channel上
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
                            5000)
                    .attr(id, 123456)//设置属性
                    .handler(new ChildChannelHandler2());
            // 发起异步连接操作
            ChannelFuture f = b.connect(host, port).sync();
            // 等待客户端链路关闭
            f.channel().closeFuture().sync();
        } finally {
            // 优雅退出,释放NIO线程组
            group.shutdownGracefully();
        }
    }

    private class ChildChannelHandler2 extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            System.out.println("客户端启动……");
            ch.pipeline().addLast("text", new ChannelInboundHandlerAdapter() {
                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                    System.out.println(ctx.channel().attr(id).get());//获取属性
                    ctx.writeAndFlush(buf);
                }

                public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
                    ByteBuf buf = (ByteBuf) msg;
                    byte[] req = new byte[buf.readableBytes()];
                    buf.readBytes(req);
                    String body = new String(req, "UTF-8");
                    System.out.println(body);
                    ScheduledFuture<?> future = ctx.channel().eventLoop().schedule(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("开启异步线程");
                        }
                    }, 1, TimeUnit.SECONDS);
//                    future.cancel(false);
                    ctx.channel().eventLoop().scheduleAtFixedRate(new Runnable() {
                        @Override
                        public void run() {
                            ctx.writeAndFlush(Unpooled.copiedBuffer("发送心跳" + new Date(), Charset.forName("UTF-8")));
                        }
                    }, 1, 5, TimeUnit.SECONDS);
                }
            });
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {

        protected void initChannel(SocketChannel ch) throws Exception {
            System.out.println("客户端启动……");
            ByteBuf bufs = Unpooled.copiedBuffer("pipeline发送的数据->", Charset.forName("UTF-8"));
            ch.pipeline().write(bufs);//通过调用ChannelPipeline的write方法将数据写入通道,但是不刷新
            ch.pipeline().addLast("text", new ChannelInboundHandlerAdapter() {
                @Override
                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                    ctx.channel().write(Unpooled.copiedBuffer("通过ChannelHandlerContext获取的channel发送的消息->",
                            Charset.forName("UTF-8")));//通过ChannelHandlerContext获取的channel发送的消息->
                    CompositeByteBuf messageBuf = Unpooled.compositeBuffer();
                    ByteBuf headerBuf = buf;
                    ByteBuf bodyBuf = buf;
                    messageBuf.addComponent(bodyBuf);//将ByteBuf实例追加到CompositeByteBuf
                    messageBuf.addComponent(headerBuf);
                    for (ByteBuf buf : messageBuf) {//遍历所有ByteBuf
                        System.out.println(buf);
                        byte[] req = new byte[buf.readableBytes()];
                        buf.readBytes(req);
                        String body = new String(req, "UTF-8");
                        System.out.println("复合缓冲区:" + body);
                    }
                    ctx.writeAndFlush(buf);
                }

                public void channelRead(ChannelHandlerContext ctx, Object msg)
                        throws Exception {
                    ByteBuf buf = (ByteBuf) msg;
                    ByteBuf copyBuf = ((ByteBuf) msg).copy();
//                    System.out.println(buf.refCnt());//返回此对象的引用计数。如果为0,则表示此对象已被释放。
//                    buf.release();//释放引用计数对象
                    for (int i = 0; i < buf.capacity(); i++) {
                        byte b = buf.getByte(i);
                        if ((char) b >= 'a' && (char) b <= 'z' || (char) b >= 'A' && (char) b <= 'Z' || (char) b == ',')
                            System.out.println("i=" + (char) b);
                    }
                    int i = buf.forEachByte(new ByteProcessor() {
                        @Override
                        public boolean process(byte value) throws Exception {
                            byte[] b = ",".getBytes();
                            if (b[0] != value)
                                return true;
                            else
                                return false;
                        }
                    });
                    System.out.println("i=" + i + " value=" + (char) buf.getByte(i));
                    ByteBuf sliced = buf.slice(0, 2);
                    sliced.setByte(0, (byte) 'h');
                    byte[] req = new byte[buf.readableBytes()];
                    buf.readBytes(req);
                    String body = new String(req, "UTF-8");
                    System.out.println(body);
                    ctx.fireChannelRead(copyBuf);
                }
            });
            ch.pipeline().addLast("text2", new ChannelInboundHandlerAdapter() {
                public void channelRead(ChannelHandlerContext ctx, Object msg)
                        throws Exception {
                    ByteBuf buf = (ByteBuf) msg;
                    byte[] req = new byte[buf.readableBytes()];
                    buf.readBytes(req);
                    String body = new String(req, "UTF-8");
                    System.out.println("text2:" + body);
                    ByteBuf bufs = Unpooled.copiedBuffer("test2发送的数据", Charset.forName("UTF-8"));
                    ctx.writeAndFlush(bufs);

                    ctx.close();
                }
            });
//            ch.pipeline().remove("text2");
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        new EchoClient().connect(port, "127.0.0.1");
    }
}

引导DatagramChannel 

    前面的引导代码示例使用的都是基于TCP协议的SocketChannel,但是Bootstrap类也可以被用于无连接的协议。为此,Netty提供了各种DatagramChannel的实现。唯一区别就是,不再调用connect()方法,而是只调用bind()方法:

        Bootstrap b = new Bootstrap();
        b.group(new OioEventLoopGroup()).channel(
                OioDatagramChannel.class).handler(
                new SimpleChannelInboundHandler<DatagramPacket>() {

                    @Override
                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                        System.out.println("打印Active");
                    }

                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
                        System.out.println("打印");
                    }
                }
        );


        // 发起异步连接操作
        ChannelFuture f = b.bind(new InetSocketAddress(port));//调用bind()方法,因为该协议是无连接的
        f.addListener(new ChannelFutureListener() {
            @Override
            public void
            operationComplete(ChannelFuture channelFuture) throws Exception {
                if (channelFuture.isSuccess()) {
                    System.out.println("通道绑定");
                } else {
                    System.err.println("绑定请求失败");
                    channelFuture.cause().printStackTrace();
                }
            }
        });
        // 等待客户端链路关闭
        f.channel().closeFuture().sync();
    

关闭

    最重要的是,你需要关闭EventLoopGroup,它将处理任何挂起的事件和任务,并且随后释放所有活动的线程。这就是调用EventLoopGroup.shutdownGracefully()方法的作用。这个方法调用将会返回一个Future,这个Future将在关闭完成时接收到通知。需要注意的是,shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的Future注册一个监听器以在关闭完成时获得通知。

参考《Netty实战》

猜你喜欢

转载自blog.csdn.net/qq_37598011/article/details/84001146