02 | Bootstrap源码分析(二)

一、Nio Client 刨析

先看下Netty4 客户端启动的demo代码:

// Configure the client.
EventLoopGroup group = new NioEventLoopGroup(1);
try {
    Bootstrap bootstrap = new Bootstrap();
    bootstrap.group(group) //【线程模型】
     .channel(NioSocketChannel.class)  //【IO模型】
    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)//连接超时时间
    .option(ChannelOption.SO_KEEPALIVE, true)//保活探测,若正常会回复ACK响应
    .option(ChannelOption.TCP_NODELAY, true) //开启Nagle算法【Nagle算法通过减少需要传输的数据包,来优化网络;提高实时性:true 提高网络利用率:false】
     .attr(AttributeKey.newInstance("clientName"), "X的netty客户端") // 用于给客户端的IO模型设置属性,即给NioSocketChannel设置属性
     .handler(new ChannelInitializer<SocketChannel>() {//【读写处理器】
         @Override
         public void initChannel(SocketChannel ch) throws Exception {
             ChannelPipeline p = ch.pipeline();
             //p.addLast(new LoggingHandler(LogLevel.INFO));
             p.addLast(new EchoClientHandler());
         }
     });
    // connect(bootstrap, HOST, PORT, 5);
    // Start the client.
    ChannelFuture f = bootstrap.connect(HOST, PORT).sync();
    // Wait until the connection is closed.
    f.channel().closeFuture().sync();
} finally {
    // Shut down the event loop to terminate all threads.
    group.shutdownGracefully();
}

01 NioEventLoopGroup创建

同Boss模式,唯一的区别是NioEventLoop的数量不一样,一般会是默认的CPU核数2
// Group线程初始化为1,不传参时默认为CPU核数
2

EventLoopGroup bossGroup = new NioEventLoopGroup(1);

也需要经历以下步骤:
1)NioEventLoopGroup实例创建
2)NioEventLoop实例创建
3)NioEventLoop#Selector创建
4)NioEventLoop#Chooser创建

02 NioSocketChannel 创建

客户端的解析并连接,对应于服务端的注册并绑定。

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.isDone()) {
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        ...
    } else {
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                ...
            }
        });
        return promise;
    }
}

1)NioSocketChannel 实例创建

逻辑同Boss模式,这里会创建一个 NioSocketChannel 实例,其构造方法中newSocket创建了jdk的ServerSocketChannel,并预设置OP_READ变量。

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        // 实例创建
        channel = channelFactory.newChannel();
        // Bootstrap 初始化channel配置
        init(channel);
    } catch (Throwable t) {
        ...
    }
    // channel注册到Group线程池,由Group线程池来处理read事件
    ChannelFuture regFuture = config().group().register(channel);
    ...
}

实例创建的具体逻辑,最终使用jdk对象创建Channel

public NioSocketChannel() {
    this(DEFAULT_SELECTOR_PROVIDER);
}

public NioSocketChannel(SelectorProvider provider) {
    this(newSocket(provider));
}

private static SocketChannel newSocket(SelectorProvider provider) {
    try {
        return provider.openSocketChannel();
    } catch (IOException e) {
        throw new ChannelException("Failed to open a socket.", e);
    }
}

参数绑定,这些参数作用于NioSocketChannel,因为客户端只有这么一个Channel。

void init(Channel channel) {
    ChannelPipeline p = channel.pipeline();
    // 添加启动时放进去的自定义handler
    p.addLast(config.handler());
    // 设置Options属性
    setChannelOptions(channel, newOptionsArray(), logger);
    setAttributes(channel, newAttributesArray());
}

2)SocketChannel#Selector关联

逻辑同Boss模式,这里的注册是指将新建出来的NioSocketChannel(JDK创建出来的Channel),与NioEventLoopGroup中NioEventLoop的unwrappedSelector相绑定。
NioEventLoop初始化时生成了Jdk的Selector,一个Selector在jdk中用来管理多个Channel,他是jdk开发出来对多个Channel操作与管理的一个功能。

final ChannelFuture initAndRegister() {
    ChannelFuture regFuture = config().group().register(channel);
}

调用AbstractChannel中内部类AbstractUnsafe的register方法,当前线程NioEventLoop作为参数,传递过去了

public ChannelFuture register(final ChannelPromise promise) {
    promise.channel().unsafe().register(this, promise);
}

绑定时关心的监听事件设置为0,表示对应Channel的FD相关事件不做监听,具体由register0中doRegister()方法实现,其调用AbstractNioChannel#doRegister方法,do相关方法就是真正干活的。

protected void doRegister() throws Exception {
  // channel注册到selector,但是监听事件为0。
  // 对client来说,监听事件应该是READ
  // 创建channel时,ACCEPT/READ事件以参数形式传递给父类AbstractNioChannel的readInterestOp成员变量,什么时候把ACCEPT/READ事件绑定到Selector上呢?最终会在AbstractNioChannel的doBeginRead方法中绑定到Selector上
  selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
  ...
}

3)NioSockerChannel#Handler关联

关联自定义handler到pipeline中,等待其nio-thread执行register0内的pipeline.invokeHandlerAddedIfNeeded()来触发。

void init(Channel channel) {
    ChannelPipeline p = channel.pipeline();
    // 添加启动时放进去的自定义handler
    p.addLast(config.handler());
    ...
}

03 NioSocketChannel 解析连接

1)NioSockerChannel#解析

NioSocketChannel注册流程结束的时候,会执行safeSetSuccess(promise)给promise对象设置返回值,触发Bootstrap给promise对象设置返回值,触发AbstractBootstrap.doResolveAndConnect中regFuture.addListener#doResolveAndConnect0方法。

private void register0(ChannelPromise promise) {
    try {
        // 触发ServerBootstrap.init中的ChannelInitializer.initChannel方法
        pipeline.invokeHandlerAddedIfNeeded();
        // Bootstrap给promise对象设置返回值,触发AbstractBootstrap.doResolveAndConnect中regFuture.addListener#doResolveAndConnect0方法,进行地址解析和连接
        safeSetSuccess(promise);
        ...       
}

doResolveAndConnect0进行地址解析和连接。

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    if (regFuture.isDone()) {
        return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
    } else {
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
            }
        });
    }
}

2)NioSockerChannel#连接

在nio-thread中进行连接操作,最终通过pipeline调用AbstractNioChannel.AbstractNioUnsafe.connect方法,

private static void doConnect(
        final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
    final Channel channel = connectPromise.channel();
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            channel.connect(remoteAddress, connectPromise);
        }
    });
}

连接服务器,数据报文经过本机网络栈->网络->服务端网络栈->服务端的OP_Accept。
但此时只是往服务器发送连接请求了,还未真正完成连接。(tcp三次握手)
关注事件为OP_CONNECT客户端连接事件,操作系统会监听tcp第二次握手的ack报文。

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
    try {
        // 连接服务器
        boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
        if (!connected) {
            selectionKey().interestOps(SelectionKey.OP_CONNECT);
        }
    ...
}

3)NioSockerChannel#事件重置

NioEventLoop拿到ack报文后,先将关注事件设置为0,并标识当前Channel为已连接状态。

private void processSelectedKey(SelectionKey k, AbstractNioChannel ch){
    if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
        // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
        // See https://github.com/netty/netty/issues/924
        int ops = k.interestOps();
        ops &= ~SelectionKey.OP_CONNECT;
        k.interestOps(ops);
        unsafe.finishConnect();
    }    
}

同时发送第三次握手报文,通过finishConnect通知Linux底层客户端已经完成连接建立了,在这之后如果获取SocketChannel是否打开、是否连接,都会返回true。

protected void doFinishConnect() throws Exception {
    if (!javaChannel().finishConnect()) {
        throw new Error();
    }
}

public boolean isActive() {
    SocketChannel ch = javaChannel();
    return ch.isOpen() && ch.isConnected();
}

4)NioSockerChannel#关注read

通过pipeline触发流水线上Handle的ChannelActive()方法,业务Handler可以埋入业务所需的逻辑,比如用户登录成功、连接建立成功等等。

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
    ...
    if (!wasActive && active) {
        pipeline().fireChannelActive();
    }
    ...
}
ChannelActive会继续触发readIfIsAutoRead相关方法,进行read事件的关注。
private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}

protected void doBeginRead() throws Exception {
    // readInterestOp为accept、read事件等,java.nio.channels.SelectionKey         // boss:OP_ACCEPT        // workor:OP_READ
    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

二、总结

客户端的启动和服务端启动复用性比较高,特别是NioEventLoopGroup|NioEventLoop等逻辑思想都是一致的,唯一的区别就在于服务端是DoBind、客户端是doResolveAndConnect,客户端这里有一个三次握手的实现,可以结合TCP的三次握手协议对照看看。

如果有条件,后期还会补充对应jdk nio、linux epoll等处理逻辑,待续。

  • 32
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 可视化表格大屏是一种数据展示界面,可以将大量的数据以图表的形式呈现在用户面前,使得用户可以快速的理解和分析数据。Bootstrap源码是可视化表格大屏的基础,通过Bootstrap的前端框架,可以轻松创建出美观、优雅的大屏界面。 Bootstrap提供了大量的CSS、JavaScript、字体库等资源,在创建大屏界面时,可以使用这些资源来提高开发效率和用户体验。同时,Bootstrap还支持响应式布局,可以根据不同的设备屏幕大小,自动调整布局和样式,使得大屏界面在各种设备上都能够得到优秀的展示效果。 在实现可视化表格大屏时,需要使用到一些常见的数据可视化技术,如柱状图、折线图、散点图、饼图等。同时,还需要考虑到用户的交互体验,如拖拽、放缩、数据筛选等,可以通过JavaScript库如D3.js、Echart.js等进行实现。 总之,可视化表格大屏bootstrap源码是一个非常有用的前端工具,提供了大量优秀的资源和技术,可以快速创建出高质量的大屏界面。无论是在数据挖掘、业务分析、决策支持等领域中,都具有重要的作用。 ### 回答2: Bootstrap是一个流行的前端框架,它提供了许多功能和组件,包括表格和可视化大屏。使用Bootstrap框架编写可视化表格大屏需要一些基本的HTML、CSS和JavaScript技能,以及对Bootstrap框架的熟悉。 首先,需要确定表格的设计和布局,包括表头、列和数据。可以使用Bootstrap的表格组件来快速创建基本的表格结构。例如: ```html <table class="table"> <thead> <tr> <th>#</th> <th>First Name</th> <th>Last Name</th> <th>Username</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>Mark</td> <td>Otto</td> <td>@mdo</td> </tr> <tr> <td>2</td> <td>Jacob</td> <td>Thornton</td> <td>@fat</td> </tr> <tr> <td>3</td> <td>Larry</td> <td>the Bird</td> <td>@twitter</td> </tr> </tbody> </table> ``` 接下来,需要将表格样式和布局进行调整,以适应大屏幕的显示。可以使用Bootstrap的网格系统来创建自适应的布局。例如: ```html <div class="row"> <div class="col-lg-8"> <table class="table"> ... </table> </div> <div class="col-lg-4"> <div id="chart"></div> </div> </div> ``` 在上面的代码中,一个8列的表格被放置在一个12列的网格系统中的8列中,另一个4列的区域用于显示图表或其他数据。 最后,我们可以使用JavaScript库来创建动态的表格和图表。例如,使用jQuery和Chart.js库来创建动态的交互式图表: ```html <div class="row"> <div class="col-lg-8"> <table class="table" id="myTable"> ... </table> </div> <div class="col-lg-4"> <canvas id="myChart"></canvas> </div> </div> <script> $(document).ready(function() { // fetch data and populate table $.getJSON('data.json', function(data) { $.each(data, function(key, value) { var row = $('<tr>'); $.each(value, function(index, element) { row.append($('<td>').text(element)); }); $('#myTable > tbody:last-child').append(row); }); }); // draw chart var ctx = document.getElementById('myChart').getContext('2d'); var chart = new Chart(ctx, { type: 'bar', data: { labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], datasets: [{ label: '# of Votes', data: [12, 19, 3, 5, 2, 3], backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)' ], borderColor: [ 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)' ], borderWidth: 1 }] }, options: { responsive: true, scales: { yAxes: [{ ticks: { beginAtZero: true } }] } } }); }); </script> ``` 上面的代码通过jQuery从一个JSON文件中获取数据,使用Chart.js库创建一个响应式的条形图,并将其嵌入到可视化大屏幕布局中的4列中。 总之,使用Bootstrap框架编写可视化表格大屏需要一些基本的HTML、CSS和JavaScript技能,以及对Bootstrap框架和所选JavaScript库的熟悉。您可以使用这些组件和库来快速创建具有灵活布局和交互式数据可视化的响应式网页应用程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值