构建连接:NioSocketChannel 是什么时候激活的

构建连接:NioSocketChannel 是什么时候激活的

目录

  • 构建连接:NioSocketChannel 是什么时候激活的

    • 1. 主线分析

      • 1.1 主线
      • 1.2 知识点
    • 2. 源码分析
      • 2.1 接收连接
      • 3.2 初始化连接

Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

在上一节中,我们分析服务器的启动过程,接下来就是开门迎客。

1. 主线分析

1.1 主线

NioEventLoop 不断的轮询,接收 OP_ACCEPT 事件;ServerBootstrapAcceptor 接收到新的连接后初始化并注册到 childGroup 上。以上工作分别是在 boss thread 和 worker thread 两个线程上执行。

  1. boss thread 线程

    • NioEventLoop 中的 selector 轮询创建连接事件(OP_ACCEPT):
    • 创建 SocketChannel
    • 初始化 SocketChannel 并从 worker group 中选择一个 NioEventLoop
  2. worker thread 线程
    • 将 SocketChannel 注册到选择的 NioEventLoop 的 selector
    • 注册读事件(OP_READ)到 selector 上
NioEventLoop#run
    -> processSelectedKeys
        -> AbstractNioMessageChannel.NioMessageUnsafe#read
            -> NioServerSocketChannel#doReadMessages
            -> pipeline#fireChannelRead
ServerBootstrapAcceptor#channelRead
    -> EventLoopGroup#register

1.2 知识点

(1)接受连接的本质

  • selector.select()/selectNow()/select(timeoutMillis) 发现OP_ACCEPT 事件,处理:
  • SocketChannel socketChannel = serverSocketChannel.accept()
  • selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
  • selectionKey.interestOps(OP_READ);

(2)ServerBootstrapAcceptor

创建连接的初始化和注册是通过 pipeline.fireChannelRead 在 ServerBootstrapAcceptor 中完成的。

2. 源码分析

NioServerSocketChannel 注册到 eventLoop 后就会启动 NioEventLoop 线程,专门处理对应 channel 的网络 IO 事件。通过 OP_ACCEPT 事件接收客户端连接 NioSocketChannel,并进行初始化。

图1:接收客户端过程

2.1 接收连接

(1)OP_ACCEPT 事件处理

processSelectedKey 负责处理 channel 的 OP_CONNECT、OP_WRITE、OP_READ、OP_ACCEPT 事件。这里我们只关注 OP_ACCEPT 事件是如何处理的。

// 分别处理 OP_CONNECT、OP_WRITE、OP_READ、OP_ACCEPT 事件
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    // 省略...
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    int readyOps = k.readyOps();
    // OP_CONNECT
    if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
        int ops = k.interestOps();
        ops &= ~SelectionKey.OP_CONNECT;
        k.interestOps(ops);
        unsafe.finishConnect();
    }

    // OP_WRITE
    if ((readyOps & SelectionKey.OP_WRITE) != 0) {
        ch.unsafe().forceFlush();
    }

    // OP_READ、OP_ACCEPT
    if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
        unsafe.read();
    }
}

说明: 可以看到 OP_READ 和 OP_ACCEPT 都是调用 unsafe.read() 处理的。不同的 Channel 对应不同的 unsafe,比如 NioServerSocketChannel 对应 NioMessageUnsafe,而 NioSocketChannel 对应 NioByteUnsafe。当然,Netty 比较巧妙的将 OP_READ 和 OP_ACCEPT 事件统一处理,这也会给我们读源码造成一些混乱。

(2)接收连接

下面,我们看一下 NioMessageUnsafe 是如何接收客户端连接的。猜也能猜到,肯定需要调用 serverSocketChannel.accept() 获取客户端连接。

// NioMessageUnsafe
private final List<Object> readBuf = new ArrayList<Object>();

@Override
public void read() {
    // 1. 接收客户端连接请求
    do {
        int localRead = doReadMessages(readBuf);
        if (localRead == 0) {
            break;
        }
        if (localRead < 0) {
            closed = true;
            break;
        }

        allocHandle.incMessagesRead(localRead);
    } while (allocHandle.continueReading());

    // 2. 接收客户端连接请求
    int size = readBuf.size();
    for (int i = 0; i < size; i ++) {
        pipeline.fireChannelRead(readBuf.get(i));
    }
    ...
}

说明: NioMessageUnsafe 的 read 方法完成了二件事:

  1. 通过 doReadMessages 接收客户端的 NioSocketChannel。当然这里的 doReadMessages 每次最多只能读一个 NioSocketChannel 对象。
  2. 触发 pipeline 的 fireChannelRead 事件完成 channel 的初始化工作 ,如有异常则触发 fireExceptionCaught。那肯定有一个 Handler 对应来处理这个 NioSocketChannel。
// NioServerSocketChannel:调用 NIO 底层接收客户连接
@Override
protected int doReadMessages(List<Object> buf) throws Exception {
    SocketChannel ch = SocketUtils.accept(javaChannel());
    if (ch != null) {
        buf.add(new NioSocketChannel(this, ch));
        return 1;
    }
    return 0;
}

说明: 真正接收客户端请求的操作则委托给了子类 NioServerSocketChannel#doReadMessages 方法完成。至此,NioServerSocketChannel 已经将请求的 NioSocketChannel 接收过来,但还未完成 channel 的初始化工作,如 handler 绑定,参数配置等。

3.2 初始化连接

上文提到 NioServerSocketChannel 在初始化的时候会绑定 ServerBootstrapAcceptor,这个 handler 完成了 channel 的初始化工作。NioServerSocketChannel 的 Pipeline 如下图:

我们直接看一下 ServerBootstrapAcceptor#channelRead 方法。主要完成 NioSocketChannel 的 TCP 参数、附加属性、Handler 配置等,基本上和 NioServerSocketChannel 一模一样。

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;

    // 1. NioSocketChannel 绑定 handler 和相关配置参数
    child.pipeline().addLast(childHandler);

    // 2. 配置 Socket 的 TCP 参数和附加属性
    setChannelOptions(child, childOptions, logger);
    for (Entry<AttributeKey<?>, Object> e: childAttrs) {
        child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
    }

    // 3. NioSocketChannel 注册到 eventLoop 上
    try {
        childGroup.register(child).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    forceClose(child, future.cause());
                }
            }
        });
    } catch (Throwable t) {
        forceClose(child, t);
    }
}

说明: 其实这段代码和 NioServerSocketChannel 初始化的代码大同小异,唯一需要注意的是 NioSocketChannel 的注册逻辑稍有区别。

  1. NioSocketChannel 注册的是 childGroup 线程。
  2. NioSocketChannel 注册到 Selector 成功后,会触发 pipeline.fireChannelActive() 事件,调用其 beginRead 方法注册 OP_READ 事件。而 NioServerSocketChannel 需要 bind 成功才会注册 OP_ACCEPT 事件。
    childGroup.register(child)
    

    注意:这里是 childGroup。Channel 是如何注册到 NioEventLoopGroup 是的详见:https://www.cnblogs.com/binarylei/p/10135712.html



每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/12640510.html

时间: 2024-10-12 19:42:53

构建连接:NioSocketChannel 是什么时候激活的的相关文章

C# 连接池开发,多连接高效应用开发,多连接自动维护管理。

本文将使用一个Github开源的组件库技术来实现连接池的操作,应用于一些情况下的频繁的网络连接操作. github地址:https://github.com/dathlin/HslCommunication 如果喜欢可以star或是fork,还可以打赏支持,打赏请认准源代码项目. 本项目目前支持C#语言和java语言,C#语言的功能比较齐全,java版本的库还在开发及完善中. nuget地址:https://www.nuget.org/packages/HslCommunication/    

[项目构建 八]babasport ActiveMQ的介绍及使用实例.

今天就来说下 这个项目中使用ActiveMQ的情况, MQ: message queue, 顾名思义就是消息队列的意思. 一: 使用场景:  消息队列在大型电子商务类网站,如京东.淘宝.去哪儿等网站有这深入的应用,队列的主要作用是消除高并发访问高峰,加快网站的响应速度.在不使用消息队列的情况下,用户的请求数据直接写入数据库,在高并发的情况下,会对数据库造成巨大的压力,同时也使得系统响应延迟加剧.在使用队列后,用户的请求发给队列后立即返回(当然不能直接给用户提示订单提交成功,京东上提示:您“您提交

Android Studio使用JDBC远程连接mysql的注意事项(附示例)

JDBC为java程序访问各种类型的关系型数据库提供了统一的接口,用户不必针对不同数据库写出不同的代码,但是使用JDBC必须得下载相应的驱动,比如我这里是要连接mysql,于是就到mysql官网去下载x相应驱动 https://dev.mysql.com/downloads/connector/j/ 这里我下载解压得到 mysql-connector-java-5.1.43-bin.jar 在Eclipse中新建java项目只需要Build Path --> Add External Archi

有关连接字符串的一些细节

关于针对不同的数据库服务器的连接字符串的编写方式,请参考下面的链接 http://connectionstrings.com/default.aspx (这是一个独立的第三方网站,整理了几乎所有的连接字符串) 关于SqlConnection.ConnectionString属性的详细介绍,请参考下面的官方文档 http://msdn.microsoft.com/zh-cn/library/system.data.sqlclient.sqlconnection.connectionstring(V

JedisPool连接池实现难点

[http://jiangwenfeng762.iteye.com/blog/1280700] [可改进的问题] 问题是jedispool有没有办法监控状态,比如说当前连接有多少,当前idle连接有多少,之类的同求.真心不想每次都构建连接然后在手动将连接返回池. [JedisPool源码] package redis.clients.jedis; import org.apache.commons.pool.impl.GenericObjectPool.Config; import redis.

web java -- 连接池 -- 概述

1. 连接池的实现原理 1. 创建连接池 首先要创建一个静态的连接池.这里的"静态"是指池中的连接时在系统初始化时就分配好的,并且不能够随意关闭.Java 提供了很多容器类可用来构建连接池,例如Vector.Stack等.在系统初始化时,根据配置创建连接并放置在连接池中,以后所使用的连接都是从该连接池中获取的,这样就可以避免连接随意建立.关闭造成的开销. 2. 分配.释放策略 创建好连接池后,需要提供一套自定义的分配.创建策略以保证数据库连接的有效复用.当客户请求数据库连接时,首先看连

手工触发jenkins自动构建

手工触发jenkins自动构建 点击项目的配置 2. 设置手工构建连接 3.在浏览器里输入来触发jenkins的项目构建

面向小数据集构建图像分类模型Keras

文章信息 本文地址:http://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html 本文作者:Francois Chollet 概述 在本文中,我们将提供一些面向小数据集(几百张到几千张图片)构造高效.实用的图像分类器的方法. 本文将探讨如下几种方法: 从图片中直接训练一个小网络(作为基准方法) 利用预训练网络的bottleneck(瓶颈)特征 fine-tune预训练网

帆软报表学习之数据连接

帆软报表FineReport中数据连接的JDBC连接池属性问题 连接池原理 在帆软报表FineReport中,连接池主要由三部分组成:连接池的建立.连接池中连接使用的治理.连接池的关闭.下面就着重讨论这三部分及连接池的配置问题. 1. 连接池原理 连接池技术的核心思想,是连接复用,通过建立一个数据库连接池以及一套连接使用.分配.治理策略,使得该连接池中的连接可以得到高效.安全的复用,避免了数据库连接频繁建立.关闭的开销. 另外,由于对JDBC中的原始连接进行了封装,从而方便了数据库应用对于连接的