Netty系列:八、引导(BootStrap)详解

了解了了ChannelPipelineChannelHandlerEventLoop之后,如何将这些零件组织起来呢?
答案就是今天说的引导(Bootstrap)

1.Bootstrap

引导类的层次结构:
netty
可以看到不管是服务器引导还是客户端引导都继承了Channel接口,很显然,服务器致力于使用一个父Channel来接受来自客户端的连接,并创建子Channel以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父 ChannelChannel来用于所有的网络交互。

AbstractBootstrap 类的完整声明是:

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> 

Bootstrap 类的完整声明是:

public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> 

Bootstrap类被用于客户端或者使用了无连接协议的应用程序中,许多方法都继承自AbstractBootstrap类。

我们来看看它的一个例子:

public class BootstrapDemo {
    public static void main(String[] args) {
        //设置EventLoopGroup用于提供处理channel的事件的EventLoop
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                //设置用于 Channel 事件和数据的ChannelInboundHandler
                .handler(new SimpleChannelInboundHandler<ByteBuf>() {
                    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
                        System.out.println("Received data");
                    }
                });
        //连接到远程主机
        ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.baidu.com",80));
        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                if(future.isSuccess()) {
                    System.out.println("Connected!");
                }else{
                    System.out.println("error");
                    future.cause().printStackTrace();
                }
            }
        });
    }
}

2.引导服务器(ServerBootstrap)

ServerBootstrapbind()方法被调用时创建了一个ServerChannel,当连接接收时,ServerChannel将会创建一个新的子Channel

public class ServerBootstrapDemo {
    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                //设置用于处理接受的子channel的IO和数据的handller
                .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
                    @Override
                    protected void channelRead0(ChannelHandlerContext ctx,
                                                ByteBuf byteBuf) throws Exception {
                        System.out.println(ctx.channel().toString());
                        System.out.println("Server Receive:"+byteBuf.toString(CharsetUtil.UTF_8));
                        ctx.writeAndFlush((Unpooled.copiedBuffer("Thanks ~", CharsetUtil.UTF_8)));
                    }
                } );
        final 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");
                    System.out.println(future.channel().toString());
                } else {
                    System.err.println("Bound attempt failed");
                    channelFuture.cause().printStackTrace();
                }
            }
        } );
    }
}

上面的程序可以运行,但是还要有个客户端才能验证出一些结论。

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 ctx, ByteBuf msg) throws Exception {
                        System.out.println("Received data");
                        System.out.println(ctx.channel());
                        System.out.println(msg.toString(CharsetUtil.UTF_8));
                    }
                });
        ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost",8080));
        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                if(future.isSuccess()) {
                    System.out.println("Connected!");
                    System.out.println(future.channel());
                    ChannelFuture future1 = future.channel().writeAndFlush( Unpooled.copiedBuffer("four you", CharsetUtil.UTF_8));
                    if(future1.isSuccess()) {
                        System.out.println("写过去了!");
                    }else{
                        System.out.println("写失败了!");
                        future1.cause().printStackTrace();
                    }
                }else{
                    System.out.println("error");
                    future.cause().printStackTrace();
                }
            }
        });

    }
}

服务端先运行,客户端后运行,然后客户端结果是:

Connected!
[id: 0x228d8d17, L:/127.0.0.1:63645 - R:localhost/127.0.0.1:8080]
写过去了!
Received data
[id: 0x228d8d17, L:/127.0.0.1:63645 - R:localhost/127.0.0.1:8080]
Thanks ~


服务端:

Server bound
[id: 0x64e9aab0, L:/0:0:0:0:0:0:0:0:8080]
[id: 0xccf20a12, L:/127.0.0.1:8080 - R:/127.0.0.1:63645]
Server Receive:four you
  • 客户端两次打印出的channel是同一个引用,正好验证了上面那句客户端(BootStrap)将最可能只需要一个单独的、没有父 ChannelChannel来用于所有的网络交互。
  • 调用服务端bind()方法会创建一个Channel,每有一个新的连接时,创建一个子channel。

3.综合应用

注意:这个例子先别细看,先把它运行起来,注意启顺序就是代码的顺序。

Server bound
Receive:four you from client!~after proxy!

Bind Ok
从客户端读到:four you from client!
从服务端读到:Thanks ~ from server

Connected!
写过去了!
received:Thanks ~ from server~after proxy!

要是你的运行结果和上面一样,那就ok,先跳过代码看下面的解释吧!
Code1

public class ServerBootstrapDemo {
    public static void main(String[] args) {
        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(ctx.channel().toString());
                        System.out.println("Receive:" + byteBuf.toString(CharsetUtil.UTF_8));
                        ctx.writeAndFlush((Unpooled.copiedBuffer("Thanks ~ from server", CharsetUtil.UTF_8)));
                    }
                });
        final 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");
                    // System.out.println(future.channel().toString());
                } else {
                    System.err.println("Bound attempt failed");
                    channelFuture.cause().printStackTrace();
                }
            }
        });
    }
}

Code2

public class ProxyServerBootStrapDemo {
    public static void main(String[] args) {
        ServerBootstrap bootstrap = new ServerBootstrap();
        EventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
                    ChannelFuture connectFutrue;

                    @Override
                    public void channelActive(final ChannelHandlerContext bigctx) throws Exception {
                        //创建一个 Bootstrap类的实例以连接到远程主机
                        Bootstrap bootstrap = new Bootstrap();
                        bootstrap.channel(NioSocketChannel.class)
                                //为入站 I/O 设置ChannelInboundHandler
                                .handler(new SimpleChannelInboundHandler<ByteBuf>() {
                                    @Override
                                    //从目标服务器那读数据
                                    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
                                        System.out.println("从服务端读到:" + msg.toString(CharsetUtil.UTF_8));
                                        String message = msg.toString(CharsetUtil.UTF_8) + "~after proxy!";
                                        bigctx.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
                                    }
                                });
                        //使用与分配给已被接受的子Channel相同的EventLoop
                        bootstrap.group(bigctx.channel().eventLoop());
                        connectFutrue = bootstrap.connect(
                                new InetSocketAddress("localhost", 8080));
                    }

                    @Override
                    //从客户端那读到数据
                    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
                        if (connectFutrue.isDone()) {
                            System.out.println("从客户端读到:" + msg.toString(CharsetUtil.UTF_8));
                            String message = msg.toString(CharsetUtil.UTF_8) + "~after proxy!";
                            connectFutrue.channel().writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
                        }
                    }
                });
        ChannelFuture future = bootstrap.bind(new InetSocketAddress(8088));
        future.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    System.out.println("Bind Ok");
                } else {
                    System.out.println("Bind error");
                    future.cause().printStackTrace();
                }
            }
        });
    }
}

Code3

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 ctx, ByteBuf msg) throws Exception {
                        //System.out.println(ctx.channel());
                        System.out.println("received:" + msg.toString(CharsetUtil.UTF_8));
                    }
                });
        ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 8088));
        future.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    System.out.println("Connected!");
                    //System.out.println(future.channel());
                    ChannelFuture future1 = future.channel().writeAndFlush(Unpooled.copiedBuffer("four you from client!", CharsetUtil.UTF_8));
                    if (future1.isSuccess()) {
                        System.out.println("写过去了!");
                    } else {
                        System.out.println("写失败了!");
                        future1.cause().printStackTrace();
                    }
                } else {
                    System.out.println("error");
                    future.cause().printStackTrace();
                }
            }
        });

    }
}

看一下简化了的流程图
netty

该程序相当于一个代理。ProxyServerBootStrapDemo类作为中间的代理,它接收客户端BootStrap的请求,转发给类ServerBootStrap。基于Netty的实现。主要是ProxyServerBootStrap中ServerChannel的子Channel的EventLoop 传递给Bootstrap的group()方法来共享该EventLoop。也就是说共享了同一个线程。没了线程间的通信,没有了上下文切换。

4.添加多个ChannelHandler

我们都在引导的过程中调用了handler()或者childHandler()方法来添加单个的ChannelHandler,有些情况下我们需要添加多个handller,通过在ChannelPipeline中将它们链接在一起来部署尽可能多的ChannelHandler。但是看了Bootstrap的源码就会发现,它只有一个ChannelHandler的成员变量。
Netty 提供了一个特殊的ChannelInboundHandlerAdapter子类:

public abstract classChannelInitializer<C extends Channel>extends ChannelInboundHandlerAdapter

例子:

public class ChannelInitializerDemo {
    public static void main(String[] args) {
        ServerBootstrap bootstrap = new ServerBootstrap();
        EventLoopGroup group = new NioEventLoopGroup();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                //注册一个 ChannelInitializerImpl 的实例来设置 ChannelPipeline
                .childHandler(new ChannelInitializerImp());
        bootstrap.bind(80);

    }
}
class ChannelInitializerImp extends ChannelInitializer {

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ch.pipeline().addLast(new ServerHandller());
    }
}

这个方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。你只需要简单地向BootstrapServerBootstrap的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel()ChannelHandler添加到一个ChannelPipeline中。

猜你喜欢

转载自blog.csdn.net/TheLudlows/article/details/79645747
今日推荐