Netty源码分析系列之新连接的接入

本文详细分析了Netty服务端如何检测新连接接入,包括通过NioEventLoop处理OP_ACCEPT事件,创建客户端channel,并将其与NioEventLoop绑定。Netty通过NioServerSocketChannel监听新连接,使用SocketUtils创建SocketChannel,包装成NioSocketChannel,并在AbstractNioChannel构造方法中初始化。新连接的客户端channel通过pipeline传播,调用ServerBootstrapAcceptor处理,注册到workerGroup的NioEventLoop上,完成绑定。
摘要由CSDN通过智能技术生成

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,阅读更多Spring源码分析Java并发编程文章。

微信公众号

1.问题

当 netty 的服务端启动以后,就可以开始接收客户端的连接了。那么在 netty 中,服务端是如何来进行新连接的创建的呢?在开始进行源码阅读之前,可以先思考以下三个问题。

  • 服务端是如何检测到有新的客户端请求接入的(后面简称新连接接入)?
  • 在 JDK 原生的 NIO 中,服务端会通过ServerSocketChannel.accept() 来为新接入的客户端创建对应的客户端 channel,那么在 netty 中服务端又是如何来处理新连接的接入的呢?
  • 在 netty 中网络 IO 的读写操作都是在 NioEventLoop 线程中进行的,那么客户端 channel 是如何和工作线程池中的 NioEventLoop 绑定的呢?

2.检测新连接接入

在上一篇文章Netty 源码分析系列之 NioEventLoop 的执行流程中,分析了 NioEventLoop 线程在启动后,会不停地去循环处理网络 IO 事件、普通任务和定时任务。在处理网络 IO 事件时,当轮询到 IO 事件类型为 OP_ACCEPT 时(如下代码所示),就表示有新客户端来连接服务端了,也就是检测到了新连接。这个时候,服务端 channel 就会进行新连接的读取。

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

可以看到,当是 OP_ACCEPT 事件时,就会调用unsafe.read() 方法来进行新连接的接入。此时 unsafe 对象是 NioMessageUnsafe 类型的实例,为什么呢?因为只有服务端 channel 才会对 OP_ACCEPT 事件感兴趣,而服务端 channel 中 unsafe 属性保存的是 NioMessageUnsafe 类型的实例。

read()方法的源码很长,但它主要干了两件事,第一:调用 doReadMessages()方法来读取连接;第二:将读取到的连接通过服务端 channel 中的 pipeline 来进行传播,最终执行每一个 handler 中的 channelRead()方法。

3.创建客户端 channel

服务端 channel 在监听到 OP_ACCEPT 事件后,会为新连接创建一个客户端 channel,后面数据的读写均是通过这个客户端 channel 来进行的。而这个客户端 channel 是通过 doReadMessages()方法来创建的,该方法是定义在 NioServerSocketChannel 中的,下面是其源码。

protected int doReadMessages(List<Object> buf) throws Exception {
   
    SocketChannel ch = SocketUtils.accept(javaChannel());
    try {
   
        if (ch != null) {
   
            // 将原生的客户端channel包装成netty中的客户端channel:NioSocketChannel
            buf.add(new NioSocketChannel(this, ch));
            return 1;
        }
    } catch (Throwable t) {
   
        // 异常日志打印等...
    }
    return 0;
}

在该方法中,首先会通过 javaChannel()获取到 JDK 原生的服务端 channel,即 ServerSocketChannel,这个原生的服务端 channel 是被保存在 NioServerSocketChannel 的ch属性中,在初始化 NioServerSocketChannel 时会对ch属性赋值(可以参考这篇文章:Netty 源码分析系列之服务端 Channel 初始化)。创建完 JDK 原生的服务端 channel 后,会通过 SocketUtils 这个工具类来创建一个 JDK 原生的客户端 channel,即 SocketChannel。SocketUtils 这个工具类的底层实现,实际上就是调用 JDK 原生的 API,即 ServerSocketChannel.accept()。

在创建完原生的 SocketChannel 后,netty 需要将其包装成 netty 中定义的服务端 channel 类型,即:NioSocketChannel。如何包装的呢?通过 new 关键字调用 NioSocketChannel 的构造方法来进行包装。在构造方法中,做了很多初始化工作。跟踪源码,发现会调用到 AbstractNioChannel 类的如下构造方法。

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
   
    // 此时的parent = NioServerSocketChannel,ch = SocketChannel(JDK原生的客户端channel),readInterestOp = OP_READ
    super(parent);
    // 保存channel和感兴趣的事件
    this.ch = ch;
    this.readInterestOp = readInterestOp;
    try {
   
        // 设置为非阻塞
        ch.configureBlocking(false);
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值