Netty3之ServerBootstrap分析

Stella981
• 阅读 519

概述

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分析

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Netty源码分析(二):服务端启动
上一篇粗略的介绍了一下netty,本篇将详细介绍Netty的服务器的启动过程。ServerBootstrap看过上篇事例的人,可以知道ServerBootstrap是Netty服务端启动中扮演着一个重要的角色。它是Netty提供的一个服务端引导类,继承自AbstractBootstrap。Serv
Stella981 Stella981
2年前
Netty创建服务器与客户端
Netty创建Server服务端Netty创建全部都是实现自AbstractBootstrap。客户端的是Bootstrap,服务端的则是ServerBootstrap。创建一个HelloServerpackageorg.examp
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Netty 学习笔记(1)
服务端启动流程packagecom.example.netty;importcom.example.netty.handler.HelloServerHandler;importio.netty.bootstrap.ServerBootstrap;importio.netty.cha
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这