netty基础08_引导类

引导类的作用是对程序进行配置;
也就是将ChannelPipeline、 ChannelHandler 、 EventLoop等组件组织起来,成为一个可实际运行的应用程序
 
netty的引导类两种,都继承自 AbstractBootstrap:
     Bootstrap    ->用于引导客户端和无连接协议
     ServerBootstrap    ->用于引导服务器
服务端致力于使用一个父 Channel 来接受来自客户端的连接, 并创建子 Channel 以用于它们之间的通信;
客户端将最可能只需要一个单独的、 没有父 Channel 的 Channel 来用于所有的网络交互。
两种应用程序类型之间通用的引导步骤由 AbstractBootstrap 处理;
特定于客户端或者服务端的引导步骤分别由 Bootstrap 或 ServerBootstrap 处理。
 
1.引导客户端 
Bootstrap 类被用于客户端或者使用了无连接协议的应用程序中。
api:
 
引导过程:
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();    //新建一个Bootstrap实例用来引导客户端
    bootstrap.group(group)                //设置 EventLoopGroup,提供用于处理 Channel事件的 EventLoop;这里用的是非阻塞流nio
        .channel(NioSocketChannel.class)    //指定要使用的channel实现;与EventLoopGroup匹配这里也要用nio的;
        .handler(new SimpleChannelInboundHandler<ByteBuf>() {    //设置由于处理入站事件的处理器
            @Override
            protected void channeRead0(
            ChannelHandlerContext channelHandlerContext,ByteBuf byteBuf) throws Exception {
                System.out.println("Received data");
            }
        } );
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.baidu.com", 80));    //连接到远程主机
future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture channelFuture)throws Exception {
        if (channelFuture.isSuccess()) {
            System.out.println("Connection established");
        } else {
            System.err.println("Connection attempt failed");
            channelFuture.cause().printStackTrace();
        }
    }
} );
 
2. 引导服务端
ServerBootstrap用来引导服务端;
 
api
 
引导过程:
ServerChannel 的实现负责创建子 Channel,这些子 Channel 代表了已被接受的连接。
NioEventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
    .channel(NioServerSocketChannel.class)
    .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx,ByteBuf byteBuf) throws Exception {
            System.out.println("Received data");
        }
    } );
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture channelFuture)throws Exception {
        if (channelFuture.isSuccess()) {
            System.out.println("Server bound");
        } else {
            System.err.println("Bound attempt failed");
            channelFuture.cause().printStackTrace();
        }
    }
} );
 
3. Channel 和 EventLoopGroup 的兼容性
netty可以使用阻塞流Oio和非阻塞流Nio;
Oio和Nio分别有其对应的Channel和EventLoopGroup实现;
可以通过前缀区分;
注意不能混用,例如:不能将Nio的EventLoopGroup配合Oio的Channel使用,否则会报 IllegalStateException;
 
4. 从 Channel 引导客户端
假设你的服务器正在处理一个客户端的请求, 这个请求需要它充当第三方系统的客户端。(例如代理服务器)
在这种情况下,需要从已经被接受的子 Channel 中引导一个客户端 Channel。
 
解决方案: 创建新的 Bootstrap 实例
    缺点:  
        必需为每个新创建的客户端 Channel 定义另一个 EventLoop,会产生额外的线程;
        被接受的子 Channel 和客户端 Channel 之间交换数据时不可避免的上下文切换 ; 
    
另一种解决方案: 将已被接受的子 Channel 的 EventLoop 传递给 Bootstrap的 group()方法来共享该 EventLoop。
    好处:因为分配给 EventLoop 的所有 Channel 都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。
    原则: 尽可能地重用 EventLoop,以减少线程创建所带来的开销;
代码实现:
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
    .channel(NioServerSocketChannel.class)
    .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {    //配置用来处理子channel的处理器
        ChannelFuture connectFuture;
        @Override
        public void channelActive(ChannelHandlerContext ctx)throws Exception {
            Bootstrap bootstrap = new Bootstrap();    //创建一个 Bootstrap类的实例以连接到远程主机
            bootstrap.channel(NioSocketChannel.class).handler(
            new SimpleChannelInboundHandler<ByteBuf>() {
                @Override
                protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in)throws Exception {
                    System.out.println("Received data");
                }
            } );
            bootstrap.group(ctx.channel().eventLoop());    //使用与分配给已被接受的子Channel相同的EventLoop
            connectFuture = bootstrap.connect(new InetSocketAddress("www.baidu.com", 80));
        }
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext,ByteBuf byteBuf) throws Exception {
        if (connectFuture.isDone()) {
            // do something with the data
        }
    }
} );
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
 
5. 在引导过程中添加多个 ChannelHandler
引导类的引导过程中,通过 handler()或者 childHandler()方法来添加Channelhandeler,但只能添加一个;
有时需要添加多个Channelhandeler;
 
Netty 提供了一个特殊的 ChannelInboundHandlerAdapter 子类:ChannelInitializer
public abstract class ChannelInitializer<C extends Channel>extends ChannelInboundHandlerAdapter
 
ChannelInitializer的抽象方法:
protected abstract void initChannel(C ch) throws Exception;
可以实现这个方法,将多个Channelhandeler添加到 ChannelPipeline;
 
作用机制:
    在引导时,通过 handler()或者 childHandler()添加 ChannelInitializer的实例;
     一旦 Channel 被注册到了它的 EventLoop 之后,就会调用ChannelInitializer的initChannel()方法;
    根据initChannel()方法的实现,可以在管道中添加多个Channelhandeler;
     在initChannel()方法返回之后, ChannelInitializer 的实例将会从 ChannelPipeline 中移除它自己。
 
代码实现:
erverBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
    .channel(NioServerSocketChannel.class)
    .childHandler(new ChannelInitializerImpl());    //用自己的初始化器作为消息处理器
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.sync();
 
//自定义的初始化器,本质上是一个消息处理器,作用是在管道中添加多个消息处理器
final class ChannelInitializerImpl extends ChannelInitializer<Channel> {
    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();    //得到管道对象
        pipeline.addLast(new HttpClientCodec());        //在管道中添加第一个消息处理器-http客户端解码器,用来按http协议解码消息
        pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));    //在管道中添加第二个消息处理器-http消息聚合器,用来将一条完整的http消息聚合在一起后交给下一步处理
    }
}
 
6. 使用 Netty 的 ChannelOption 和属性
1)ChannelOption
ChannelOption用来保存Channel的配置;
常用的配置: 如keep-alive、超时属性以及缓冲区设置等;
可以使用引导类的option()方法来将 ChannelOption属性应用到引导;
添加到引导中的ChannelOption属性会自动应用到引导所创建的每个Channel中;
 
2)属性
有时需要在Channel中传递额外的属性;
Netty为此提供了 AttributeMap 抽象(一个由 Channel 和引导类提供的集合) 以及 AttributeKey<T>(一个用于插入和获取属性值的泛型类)。
 
 
3)代码实现
final AttributeKey<Integer> id = new AttributeKey<Integer>("ID");    //使用AttributeKey给Channel添加额外属性
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup())
    .channel(NioSocketChannel.class)
    .handler(new SimpleChannelInboundHandler<ByteBuf>() {
        @Override
        public void channelRegistered(ChannelHandlerContext ctx)
        throws Exception {
            Integer idValue = ctx.channel().attr(id).get();
            // do something with the idValue
        }
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext,
        ByteBuf byteBuf) throws Exception {
            System.out.println("Received data");
        }
    });
bootstrap.option(ChannelOption.SO_KEEPALIVE,true)        //用option()方法给Channel添加ChannelOption中的属性
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
bootstrap.attr(id, 123456);    //调用attr()方法给额外属性赋值
ChannelFuture future = bootstrap.connect(
new InetSocketAddress("www.baidu.com", 80));
future.syncUninterruptibly();
 
7.引导无连接协议
前面的引导代码示例使用的都是基于 TCP 协议的 SocketChannel;
Bootstrap 类也可以被用于无连接的协议;
为此,Netty 提供了各种 DatagramChannel 的实现。
唯一区别就是,不再调用 connect()方法,而是只调用 bind()方法
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new OioEventLoopGroup())
    .channel(OioDatagramChannel.class)
    .handler(new SimpleChannelInboundHandler<DatagramPacket>(){
        @Override
        public void channelRead0(ChannelHandlerContext ctx,
        DatagramPacket msg) throws Exception {
            // Do something with the packet
        }
    }
);
ChannelFuture future = bootstrap.bind(new InetSocketAddress(0));    //调用 bind()方法,因为该协议是无连接的
 
8.关闭
关闭netty程序时,需要关闭 EventLoopGroup;
关闭 EventLoopGroup时需要干净地释放资源: 处理任何挂起的事件和任务,并且随后释放所有活动的线程;
EventLoopGroup的shutdownGracefully()方法用来做这一工作;
shutdownGracefully()方法是一个异步操作,返回一个Future;
可以给返回的Future注册一个监听器以在关闭完成时获得通知;
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class);
...
Future<?> future = group.shutdownGracefully();    //优雅关闭EventLoopGroup
// block until the group has shutdown
future.syncUninterruptibly();

 

 
 
 
 
posted @ 2020-05-29 16:59  L丶银甲闪闪  阅读(207)  评论(0编辑  收藏  举报