netty实战笔记 第八章 引导

8.1 bootstrap类

先看下引导类的层次结构
在这里插入图片描述
服务器端使用一个父Channel来接受来自客户端的连接,并创建子Channel用于他们之间的通信。客户端将最可能只需要一个单独的,没有父Channel的Channel来用于所有的网络交互。

  • 为什么引导类是Cloneable的?

在这里插入图片描述

AbstractBootStrap类的完成声明:
public abstract class AbstractBootstrap <B extends AbstractBootstrap<B,C> , C extends Channel>
在这个签名中,子类型B 是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法)。

其子类的声明如下:
public class Bootstrap
ext ends AbstractBootstrap<Bootstrap,Channel>

public class ServerBootstrap
ext ends AbstractBootstrap<ServerBootstrap,ServerChannel>

8.2 引导客户端 和 无连接协议。

我觉得应该用客户端引导。引导是名词。

Bootstrap类,被用于客户端或者使用了无连接的的应用程序中。
Bootstrap的API如下:

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

8.2 引导客户端

Bootstrap类负责为客户端和使用无连接协议的应用创建Channel。
在这里插入图片描述

package com.moyang;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

public class BootstrapDemo {

    public static void main (String[] args) {

        EventLoopGroup group = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();

        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new SimpleChannelInboundHandler<ByteBuf>() {
                    protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
                        System.out.println("received data ...");
                    }
                });
        // 连接到远程主机
        ChannelFuture future = bootstrap.connect(new InetSocketAddress("127.0.0.1", 80));
        future.addListener(new ChannelFutureListener() {
            public void operationComplete (ChannelFuture channelFuture) throws Exception {
                if(channelFuture.isSuccess()) {
                    System.out.println("operation success !");
                } else {
                    System.out.println("operation failed!");
                }
            }
        });
    }
}

8.3 引导服务器

8.3.1 引导服务器的API

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

8.3.2 引导服务器

package com.moyang;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class ServerBootstrapDemo {

    public static void main (String[] args) {
        ServerBootstrap bootstrap = new ServerBootstrap();

        EventLoopGroup group = new NioEventLoopGroup();

        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                // 设置用于处理子Channel 的IO数据的ChannelInboundHandler
                .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
                    protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
                        System.out.println("received data !");
                    }
                });
        ChannelFuture future = bootstrap.bind(80);
        future.addListener(new ChannelFutureListener() {
            public void operationComplete (ChannelFuture channelFuture) throws Exception {
                if(channelFuture.isSuccess()){
                    System.out.println("server bound !");
                }else{
                    System.out.println(" faild !");
                }
            }
        });
    }
}

ServerChannel的实现服务创建子Channel。这些子Channel代表已经接受的连接。

在这里插入图片描述

8.4 从Channel引导客户端

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

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

解决方案图例:
在这里插入图片描述

一般准则: 尽可能地重用EventLoop,以减少线程创建所带来的开销

代码如下:

package com.moyang;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * 需求:
 * 服务器端接收客户端的请求: 需要服务端充当第三方系统的客户端。
 * <p>
 * 解决方案:
 * 通过将已接受的子channel的EventLoop传递给我Bootstrap的group()方法来共享该EventLoop。
 * 这个方案: 反映了netty的一般准则。尽可能的重用EventLoop,以减少线程创建带来的开销。
 */

public class BootDemo {

    public static void main (String[] args) {

        final ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
                    ChannelFuture connectFuture;

                    protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
                        ChannelHandlerContext ctx;
                        if(connectFuture.isDone()) {
                            System.out.println("has received all data ....");
                        }
                    }

                    @Override
                    public void channelActive (ChannelHandlerContext ctx) throws Exception {
                        // 新建一个客户端用以连接到目标地址。
                        Bootstrap bs = new Bootstrap();
                        bs.channel(NioSocketChannel.class)
                                .handler(new SimpleChannelInboundHandler<ByteBuf>() {
                                    protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
                                        System.out.println("received data..");
                                    }
                                });
                        // 使用的服务端的eventLoop。
                        bs.group(ctx.channel().eventLoop());
                        connectFuture = bs.connect(new InetSocketAddress("www.manning.com", 80));
                    }
                });

        ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
        future.addListener(new ChannelFutureListener() {
            public void operationComplete (ChannelFuture channelFuture) throws Exception {
                if(channelFuture.isSuccess()) {
                    System.out.println("server bound!");
                } else {
                    System.out.println(" bind attempt failed");
                    channelFuture.cause().printStackTrace();
                }
            }
        });
    }
}

8.5 在引导过程中添加多个ChannelHandler

当一个要支持很多协议的应用程序将会有很多的CHannelHandler,那么仅仅使用Handler或者childHandler方法就不够了。
netty提供了一个ChannelInboundHandlerAdapter子类:public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter. 它提供了方法: protected abstract void initChannel(C ch) throws Exception.这个方法可以将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。
一旦Channel被注册到它的EventLoop之后,就会电泳initChannel版本。在方法返回之后,ChannelInitializer实例将会从ChannelPipeline中移除自己。
来个栗子吧。

package com.moyang2;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;

public class AddMutilChannelHandler {

    public static void main (String[] args) throws InterruptedException {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<Channel>() {
                    protected void initChannel (Channel channel) throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        pipeline.addLast(new HttpClientCodec());
                        pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
                    }
                });
        ChannelFuture fu = serverBootstrap.bind(8080).sync();
//        fu.addListener(....)
        if(fu.isSuccess()){
//            doSomething..
        }
    }

}

8.6 使用Netty的ChannelOption的属性

可以通过option()方法来将ChannelOption应用到引导所创建的所有Channel。ChannelOption包括了底层连接的详细信息。
有时chanel这样的组件可能会在Netty生命周期之外被使用。在某些常用的属性和数据不可用时,netty提供了AttributeMap抽象(一个由Channel和引导类提供的集合)以及AttributeKey(一个用于掺入和获取属性值的泛型类)。通过这些工具,便可以安全的将任何类型的数据项和客户端的服务器Channel(包含ServerChannel和子Channel)相关联了。

demo:

package com.moyang2;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;

public class ChannelOptaionDemo {

    public static void main (String[] args) throws InterruptedException {
        final AttributeKey<Integer> attributeKey = AttributeKey.newInstance("ID");
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .handler(new SimpleChannelInboundHandler<ByteBuf>() {
                    @Override
                    public void channelRegistered (ChannelHandlerContext ctx) throws Exception {
                        // 使用AttributeKey检索属性以及它的值。
                        System.out.println(ctx.channel().attr(attributeKey).get());
                    }

                    protected void channelRead0 (ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
                        // doSomethings...
                    }
                })
                // 存储属性值
                .attr(attributeKey,123456);
        ChannelFuture future = bootstrap.bind("127.0.0.1", 8080);
        future.syncUninterruptibly();
    }
}

8.7 引导DatagramChannel(数据报文Channel)

之前的实例都是基于TCP协议的SOcketChannel。BootStrap也常用于无连接的协议。Netty提供了DatagramChannel的实现。
唯一的区别是,不需要调用connect()方法,而是只调用bind()方法。

package com.moyang2;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.oio.OioDatagramChannel;

public class DatagramChannelDemo {
    public static void main (String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();

        ChannelFuture future = bootstrap.group(new OioEventLoopGroup())
                .channel(OioDatagramChannel.class)
                .handler(new SimpleChannelInboundHandler<DatagramPacket>() {
                    protected void channelRead0 (ChannelHandlerContext channelHandlerContext, DatagramPacket datagramPacket) throws Exception {
                        //dosomethings
                    }
                }).bind(0).sync();
    }
}

8.8 关闭

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

最后

如果你觉得写的还不错,就关注下公众号呗,关注后,有点小礼物回赠给你。
你可以获得5000+电子书,java,springCloud,adroid,python等各种视频教程,IT类经典书籍,各种软件的安装及破解教程。
希望一块学习,一块进步!
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方_小_白

谢谢金主子,记得关注方家小白哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值