Netty3之ServerBootstrap分析

概述

ServerBootstrap是Netty提供的一个服务端工具类,通过设置ChanneFactory,ChannelPipelineFactory,用户可以很方便的启动一个服务端。

ServerBootstrap是做什么的

ServerBootstrap是一个帮助类,用来创建服务端的Channel以监听来自客户端的连接。

面向连接

ServerBootstrap只支持面向连接的传输,比如TCP/IP,如果是没有连接的传输,比如UDP/IP这样,就需要使用ConnectionlessBootstrap。

Parent and Child Channel

ServerBootstrap通过bind()方法使用属性ChannelFactory来创建服务端的Channel,以监听并且accept客户端的连接。服务端的Channel被称为parent Channel,来自客户端的连接被称为child Channel。

配置Channel

使用Options来设置parent and child Channel的可选参数,其中child Channel需要使用child.前缀。

ServerBootstrap b = ...;

// Options for a parent channel
b.setOption("localAddress", new InetSocketAddress(8080));
b.setOption("reuseAddress", true);

// Options for its children
b.setOption("child.tcpNoDelay", true);
b.setOption("child.receiveBufferSize", 1048576);

设置Channel Pipeline

Netty使用ChannelPipeline来处理和流转IO事件。parent Channel的Pipeline是由ServerBootstrap内部创建的,并且加入Binder的内部类作为ChannelHandler。我们可以设置ServerBootstrap的属性parentHandler,一旦设置之后parentHandler就会被追加到parent Pipeline中用来我们自定义的ChannelHandler。

child Pipeline有两种方式可以设置,一种方式是通过setPipelineFactory(ChannelPipelineFactory)设置,每个child Channel都会通过ChannelPipelineFactory创建新的ChannelPipeline,是推荐的方式。另一种方式通过setPipeline(ChannelPipeline)设置,这个方法内部会创建ChannelPipelineFactory,复用设置的ChannelPipeline的内部ChannelHandler。

不同配置,不同Channel

ServerBootstrap本身并不是占用资源,而是交给ChannelFactory来处理,官网文档说可以使用同一个ChannelFactory来创建多个ServerBootstrap,每个ServerBootstrap都可以使用不同的配置,从而创建不同的Channel(没见过这种需要。)

ServerBootstrap创建和启动过程

Netty为我们提供ServerBootstrap作为工具来方便的启动服务端,使用起来也很简单,一般分为这几步:

  • 创建ChannelFactory
  • 创建ChannelPipelineFactory
  • 创建ServerBootstrap实例、设置ChannelFactory和ChannelPipelineFactory
  • 调用bind方法

1、创建ChannelFactory

这个ChannelFactory在启动时候会创建parent Channel也就是服务的Channel以监听来自客户端的连接。

ChannelFactory是用来创建parent Channel

2、创建ChannelPipelineFactory

在accept Channel之后会使用这个ChannelPipelineFactory创建ChannelPipeline用来处理IO事件。常见的使用方法是使用Channels.pipeline()创建一个DefaultChannelPipeline,我们往里添加自定义的ChannelHandler即可。

ChannelPipelineFactory是用来服务child Channel

3、设置ChannelFactory和ChannelPipelineFactory

创建ServerBootstrap实例,设置ChannelFactory和ChannelPipelineFactory属性。

4、调用bind()方法启动,绑定在指定端口。

官网示例(也是常用使用方式,Dubbo就是这样的,设计的简约而不简单)

// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(
        new NioServerSocketChannelFactory(
                Executors.newCachedThreadPool(),
                Executors.newCachedThreadPool()));

// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
    public ChannelPipeline getPipeline() throws Exception {
        return Channels.pipeline(new EchoServerHandler());
    }
});

// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(port));

bind()绑定过程

ServerBootstrap通过调用bind()方法来启动服务端。绑定方法很简单,首先创建parent的ChannelPipeline,然后使用ChannelFactory创建Channel,结束。

public Channel bind(final SocketAddress localAddress) {
    ChannelFuture future = bindAsync(localAddress);

    // Wait for the future.
    future.awaitUninterruptibly();
    if (!future.isSuccess()) {
        future.getChannel().close().awaitUninterruptibly();
        throw new ChannelException("Failed to bind to: " + localAddress, future.getCause());
    }

    return future.getChannel();
}

public ChannelFuture bindAsync(final SocketAddress localAddress) {
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    // 设置bossPipeline
    Binder binder = new Binder(localAddress);
    ChannelHandler parentHandler = getParentHandler();

    ChannelPipeline bossPipeline = pipeline();
    bossPipeline.addLast("binder", binder);
    if (parentHandler != null) {
        bossPipeline.addLast("userHandler", parentHandler);
    }

    // 创建parent Channel
    Channel channel = getFactory().newChannel(bossPipeline);
    final ChannelFuture bfuture = new DefaultChannelFuture(channel, false);
    binder.bindFuture.addListener(new ChannelFutureListener() {
        public void operationComplete(ChannelFuture future) throws Exception {
            if (future.isSuccess()) {
                bfuture.setSuccess();
            } else {
                // Call close on bind failure
                bfuture.getChannel().close();
                bfuture.setFailure(future.getCause());
            }
        }
    });
    return bfuture;
}

分析方法过程:

  1. 创建parent ChannelPipeline,添加Binder、外部设置的parentChannelHandler,这个Pipeline是给parent Channel服务的
  2. 使用ChannelFactory创建Channel,是parent Channel

Binder类是ServerBootstrap内部类,是一个UpstreamHandler,处理三个事件:channelOpen、childChannelOpen、exceptionCaught。

NioServerSocketChannelFactory创建NioServerSocketChannel,在NioServerSocketChannel的构造函数中会触发一个ChannelOpen的事件传入Pipeline中,这个Pipeline就是在ServerBootstrap的bind()方法里创建的parent Pipeline。而ChannelOpen是一个UpstreamEvent,因此Binder类就会执行相应的逻辑。

关键代码

// NioServerSocketChannel的构造函数中执行
socket = ServerSocketChannel.open();
socket.configureBlocking(false);
fireChannelOpen(this);		

// Binder类的channelOpen方法,最后执行channel的bind方法
evt.getChannel().bind(localAddress).addListener(new ChannelFutureListener() {
    public void operationComplete(ChannelFuture future) throws Exception {
        if (future.isSuccess()) {
            bindFuture.setSuccess();
        } else {
            bindFuture.setFailure(future.getCause());
        }
    }
});


// NioServerSocketChannel的bind方法
channel.getPipeline().sendDownstream(new DownstreamChannelStateEvent(channel, future, ChannelState.BOUND, localAddress));


// NioServerSocketPipelineSink,调用NioServerBoss的bind方法
case BOUND:
    if (value != null) {
        ((NioServerBoss) channel.boss).bind(channel, future, (SocketAddress) value);
    } else {
        ((NioServerBoss) channel.boss).close(channel, future);
    }
    break;

// NioServerBoss中创建RegisterTask
void bind(final NioServerSocketChannel channel, final ChannelFuture future,
          final SocketAddress localAddress) {
    registerTask(new RegisterTask(channel, future, localAddress));
}

// RegisterTask中执行nio的bind和注册到Selector,属性的nio知识点
channel.socket.socket().bind(localAddress, channel.getConfig().getBacklog());
bound = true;

future.setSuccess();
fireChannelBound(channel, channel.getLocalAddress());
channel.socket.register(selector, SelectionKey.OP_ACCEPT, channel);

流程分析:

  1. NioServerSocketChannel构造方法创建Java nio的ServerSocketChannel;
  2. NioServerSocketChannel构造方法中触发OPEN的UpstreamEvent;
  3. Binder类中channelOpen()方法中调用Channel的bind()方法;
  4. NioServerSocketChannel的bind()方法触发一个BOUND的DownstreamEvent;
  5. NioServerSocketPipelineSink,调用NioServerBoss的bind()方法;
  6. NioServerBoss中创建RegisterTask,RegisterTask是一个Runnable对象,run()方法中执行NIO的相关操作,真正处理IO的地方
  7. ServerSocketChannel的bind方法,ServerSocketChannel注册到Selector,事件是OP_ACCEPT

总体的看来,ServerBootstrap内部将任务交给了Binder和ChannelFactory,进一步说其实是Channel来处理。通过一系列的事件触发,最终调用JDK NIO的ServerSocketChannel完成启动并注册监听。

ServerBootstrap没做什么,都是ServerChannelFactory在处理,想要理清楚Netty的服务端,就得剖析NioServerSocketChannelFactory。

思考一下

为什么不直接在ServerBootstrap的bind方法里直接执行Channel的bind方法呢?我觉得是想把复杂的逻辑剥离出来到Binder这个内部类里。may be

相关阅读

NioServerSocketChannelFactory分析

转载于:https://my.oschina.net/cregu/blog/2874332

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty5.0 架构剖析和源码解读 作者:李林锋 版权所有 email neu_lilinfeng@ © Netty5.0 架构剖析和源码解读1 1. 概述2 1.1. JAVA 的IO演进2 1.1.1. 传统BIO通信的弊端2 1.1.2. Linux 的网络IO模型简介4 1.1.3. IO复用技术介绍7 1.1.4. JAVA的异步IO8 1.1.5. 业界主流的NIO框架介绍10 2.NIO入门10 2.1. NIO服务端10 2.2. NIO客户端13 3.Netty源码分析16 3.1. 服务端创建16 3.1.1. 服务端启动辅助类ServerBootstrap16 3.1.2. NioServerSocketChannel 的注册21 3.1.3. 新的客户端接入25 3.2. 客户端创建28 3.2.1. 客户端连接辅助类Bootstrap28 3.2.2. 服务端返回ACK应答,客户端连接成功32 3.3. 读操作33 3.3.1. 异步读取消息33 3.4. 写操作39 3.4.1. 异步消息发送39 3.4.2. Flush操作42 4.Netty架构50 4.1. 逻辑架构50 5. 附录51 5.1. 作者简介51 5.2. 使用声明51 1. 概述 1.1.JAVA 的IO演进 1.1.1. 传统BIO通信的弊端 在JDK 1.4推出JAVANIO1.0之前,基于JAVA 的所有Socket通信都采用 BIO 了同步阻塞模式( ),这种一请求一应答的通信模型简化了上层的应用开发, 但是在可靠性和性能方面存在巨大的弊端。所以,在很长一段时间,大型的应 C C++ 用服务器都采用 或者 开发。当并发访问量增大、响应时间延迟变大后, 采用JAVABIO作为服务端的软件只有通过硬件不断的扩容来满足访问量的激 增,它大大增加了企业的成本,随着集群的膨胀,系统的可维护性也面临巨大 的挑战,解决这个问题已经刻不容缓。 首先,我们通过下面这幅图来看下采用BIO 的服务端通信模型:采用BIO 通信模型的 1connect NewThread1 WebBrowse 2connect 2handle(Req) WebBrowse 3connect Acceptor NewThread2 WebBrowse WebBrowse 4connect NewThread3 3sendResponsetopeer NewThread4 图1.1.1-1 BIO通信模型图 服务端,通常由一个独立的Accepto 线程负责监听客户端的连接,接收到客户 端连接之后为客户端连接创建一个新的线程处理请求消息

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值