netty之关闭连接源码分析

news2024/12/30 4:22:51

写在前面

本文看下netty关闭channel相关源码。

1:前置准备

为了测试,我们需要使用netty源码中examples模块的echoserver和echoclient,但是echoclient因为会不断的发送消息,并不会断开连接,所以,我们需要修改为如下:

package io.netty.example.echo;

public final class EchoClientForDebugClose {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final String HOST = System.getProperty("host", "127.0.0.1");
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.git
        final SslContext sslCtx;
        if (SSL) {
            sslCtx = SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }

        // Configure the client.
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .option(ChannelOption.TCP_NODELAY, true)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
//                     p.addLast(new EchoClientHandler()); 因为要debug通道close时server端的逻辑,所以这里直接注释
                 }
             });

            // Start the client.
            ChannelFuture f = b.connect(HOST, PORT).sync();

            // Wait until the connection is closed.
//            f.channel().closeFuture().sync(); 因为要debug通道close时server端的逻辑,所以这里直接注释
        } finally {
            // Shut down the event loop to terminate all threads.
            group.shutdownGracefully();
        }
    }
}

其中代码group.shutdownGracefully();就是客户端关闭通道的代码。当该代码执行后会触发server端的op_read事件,只不过此时读取到的数据量是-1,代表正常的EOF。接着就可以正式开始debug源码了。

2:正戏

首先在server端代码group.shutdownGracefully();和服务端代码io.netty.channel.nio.NioEventLoop#run打断点,首先启动server,此时我们先mute server端代码,接着启动client端代码, client端代码进入debug:
在这里插入图片描述
接着取消server端代码断点mute,就可以开始调试了:
在这里插入图片描述
执行到代码:

// io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
    final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
    i// ...

    try {
        // ...

        // Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
        // to a spin loop op_accept接收连接事件,op_read读数据事件的话为true,进if执行
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read(); // 读数据
        }
    } catch (CancelledKeyException ignored) {
        unsafe.close(unsafe.voidPromise());
    }
}

继续:

// io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read
public final void read() {
    final ChannelConfig config = config();
    if (shouldBreakReadReady(config)) {
        clearReadPending();
        return;
    }
    final ChannelPipeline pipeline = pipeline();
    final ByteBufAllocator allocator = config.getAllocator(); // 这是一个自适应大小的分配器
    final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle(); // 用来动态调整buf的实际大小,是一个guess的过程,连续读的多就变大,反之变小
    allocHandle.reset(config);

    ByteBuf byteBuf = null;
    boolean close = false;
    try {
        do {
            // ...
            // doReadBytes是真的读数据,lastBytesRead记录读到的总字节数以及当次读到的字节数,以作为动态调整buf大小的依据
            allocHandle.lastBytesRead(doReadBytes(byteBuf));
            if (allocHandle.lastBytesRead() <= 0) { // 读到的数据小于0,则说明是客户端关闭连接
                // nothing was read. release the buffer. 数据清理,释放堆外内存
                byteBuf.release();
                byteBuf = null;
                close = allocHandle.lastBytesRead() < 0;
                if (close) {
                    // There is nothing left to read as we received an EOF.
                    readPending = false;
                }
                break;
            }
            // ...
        } while (allocHandle.continueReading()); // allocHandle.continueReading() 代表在满足特定条件下会尝试多次重复读取数据
        // ...

        if (close) { // 关闭逻辑处理
            closeOnRead(pipeline);
        }
    } catch (Throwable t) { // 异常关闭,同样是
        // ...
    } finally {
        // ...
    }
}

方法doReadBytes(byteBuf)当返回-1,代表EOF了,即客户端正常关闭了channel,所以接下来的if就为true了,内部执行一些资源释放的工作。主要看方法closeOnRead(pipeline);

// io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#closeOnRead
private void closeOnRead(ChannelPipeline pipeline) {
    if (!isInputShutdown0()) {
        if (isAllowHalfClosure(config())) { // 半开,忽略
            shutdownInput();
            pipeline.fireUserEventTriggered(ChannelInputShutdownEvent.INSTANCE);
        } else {
            close(voidPromise()); // 进一步执行关闭
        }
    } else {
        inputClosedSeenErrorOnRead = true;
        pipeline.fireUserEventTriggered(ChannelInputShutdownReadComplete.INSTANCE);
    }
}

看方法close(voidPromise());:

// io.netty.channel.AbstractChannel.AbstractUnsafe#close(io.netty.channel.ChannelPromise, java.lang.Throwable, java.nio.channels.ClosedChannelException, boolean)
private void close(final ChannelPromise promise, final Throwable cause,
                   final ClosedChannelException closeCause, final boolean notify) {
    // ...
    if (closeExecutor != null) {
        // ...
    } else {
        try {
            // Close the channel and fail the queued messages in all cases.
            doClose0(promise); // do了,真的去关了
        } finally {
            // ...
        }
        if (inFlush0) {
            invokeLater(new Runnable() {
                @Override
                public void run() {
                    fireChannelInactiveAndDeregister(wasActive);
                }
            });
        } else {
            fireChannelInactiveAndDeregister(wasActive); // 触发事件了,通道变为不激活,以及取消事件注册
        }
    }
}

方法doClose0(promise);,又do了,看到do就有点莫名兴奋,有没有?:

// io.netty.channel.socket.nio.NioSocketChannel#doClose
protected void doClose() throws Exception {
    super.doClose();
    javaChannel().close(); // 本质了执行Java NIO channel的close方法,完成通道关闭
}

javaChannel().close();就已经是JavaNIO的代码了,内部会最终执行如下位置,关闭通道的同时也会取消selection key事件的注册:

// java.nio.channels.spi.AbstractSelectableChannel#implCloseChannel
protected final void implCloseChannel() throws IOException {
    implCloseSelectableChannel();
    synchronized (keyLock) {
        int count = (keys == null) ? 0 : keys.length;
        for (int i = 0; i < count; i++) {
            SelectionKey k = keys[i];
            if (k != null)
                k.cancel();
        }
    }
}

写在后面

参考文章列表

netty之导入源码到idea。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2238114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux(CentOS)yum update -y 事故

CentOS版本&#xff1a;CentOS 7 事情经过&#xff1a; 1、安装好CentOS 7&#xff0c;系统自带JDK8&#xff0c;版本为&#xff1a;1.8.0_181 2、安装好JDK17&#xff0c;版本为&#xff1a;17.0.13 3、为了安装MySQL执行了 yum update -y&#xff08;这个时候不知道该命令的…

基于SpringBoot的“在线考试系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“在线考试系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统登录界面图 用户注册界面图 管…

Ubuntu 的 ROS 操作系统安装与测试

引言 机器人操作系统&#xff08;ROS, Robot Operating System&#xff09;是一个用于开发机器人应用的开源框架&#xff0c;它提供了一系列功能丰富的库和工具&#xff0c;能够帮助开发者构建和控制机器人。 当前&#xff0c;ROS1的最新版本为Noetic Ninjemys&#xff0c;专为…

学习threejs,将多个网格合并成一个网格

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.Geometry 几何体1.2 …

vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法

1、先上个截图&#xff1a; 说明&#xff1a;拖动上面的分隔栏就可以实现&#xff0c;改变左右区域的大小。 2、上面的例子来自官网的&#xff1a; Container 布局容器 | Element Plus 3、拖动的效果来自&#xff1a; https://juejin.cn/post/7029640316999172104#heading-1…

7个常用的JavaScript数组操作进阶用法

文章目录 1、查找数组中的最大值方法一&#xff1a;使用 Math.max 和展开运算符方法二:使用 for 循环逐一比较 2、查找数组中的第二大值方法一&#xff1a;排序后取第二大值方法二&#xff1a;遍历找到第二大值 3、去除数组中的重复项4、合并两个有序数组并保持有序5、旋转数组…

前深度学习时代-经典的推荐算法

参考自《深度学习推荐系统》—— 王喆&#xff0c;用于学习记录。 1.协同过滤 “协同过滤”就是协同大家的反馈、评价和意见一起对海量的信息进行过滤&#xff0c;从中筛选出目标用户可能感兴趣的信息的推荐过程。 基于用户相似度进行推荐的协同过滤算法 UserCF 用户相似度…

FPGA学习笔记#6 Vitis HLS For循环的优化(2)

本笔记使用的Vitis HLS版本为2022.2&#xff0c;在windows11下运行&#xff0c;仿真part为xcku15p_CIV-ffva1156-2LV-e&#xff0c;主要根据教程&#xff1a;跟Xilinx SAE 学HLS系列视频讲座-高亚军进行学习 学习笔记&#xff1a;《FPGA学习笔记》索引 FPGA学习笔记#1 HLS简介及…

MTK6833/MT6833(天玑700)安卓核心板_联发科5G智能通讯模块安卓主板定制

天玑700定位主流级&#xff0c;让5G技术惠及所有人。 MT6833采用7nm制程工艺&#xff0c;旨在为大众市场带来先进的5G功能和体验&#xff0c;依托5G双载波聚合技术&#xff08;2CC&#xff09;及双5G SIM卡功能&#xff0c;实现优异的功耗表现及实时连网功能。 CPU部分由2个2…

Spring Boot框架的知识分类技术解析

2 开发技术 2.1 VUE框架 Vue.js&#xff08;读音 /vjuː/, 类似于 view&#xff09; 是一套构建用户界面的渐进式框架。 Vue 只关注视图层&#xff0c; 采用自底向上增量开发的设计。 Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。 2.2 Mysql数据库 …

面试:TCP、UDP如何解决丢包问题

文章目录 一、TCP丢包原因、解决办法1.1 TCP为什么会丢包1.2 TCP传输协议如何解决丢包问题1.3 其他丢包情况&#xff08;拓展&#xff09;1.4 补充1.4.1 TCP端口号1.4.2 多个TCP请求的逻辑1.4.3 处理大量TCP连接请求的方法1.4.4 总结 二、UDP丢包2.1 UDP协议2.1.1 UDP简介2.1.2…

关于我、重生到500年前凭借C语言改变世界科技vlog.17——字符函数字符串函数

文章目录 1.字符函数1.1 字符分类函数1.1.1 islower 1.2 字符转换函数1.2.1 tolower 2.字符串函数2.1 strlen2.2 strcpy和strncpy2.3 strcat和strncat2.4 strcmp和strncmp2.5 strstr2.6 strtok2.7 strerror 希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&am…

可视化建模与UML《类图实验报告》

史铁生&#xff1a; 余华和莫言扛着我上火车&#xff0c; 推着走打雪仗&#xff0c; 还带我偷西瓜&#xff0c; 被人发现后他们拔腿就跑&#xff0c; 却忘了我还在西瓜地里。 一、实验目的&#xff1a; 1、熟悉类图的构件事物。 2、熟悉类之间的泛化、依赖、聚合和组合关系…

基于梧桐数据库的实时数据分析解决方案

一、背景 在当今信息时代&#xff0c;数据的价值不言而喻。然而&#xff0c;处理海量数据并将其转化为有意义的洞察力是一项艰巨的任务。传统的数据处理方法已经无法满足我们日益增长的需求。为了满足这一挑战&#xff0c;实时数据处理系统应运而生。 ​ 实时数据处理系统是一…

javascript实现国密sm4算法(支持微信小程序)

概述&#xff1a; 本人前端需要实现sm4计算的功能&#xff0c;最好是能做到分多次计算。 本文所写的代码在现有sm4的C代码&#xff0c;反复测试对比计算过程参数&#xff0c;成功改造成sm4的javascript代码&#xff0c;并成功验证好分多次计算sm4数据 测试平台&#xff1a; …

jmeter常用配置元件介绍总结之jsr223执行python脚本

系列文章目录 安装jmeter jmeter常用配置元件介绍总结之jsr223执行python脚本 1.安装jsr223执行python插件2.基础语法介绍2.1.log2.2.parameters向脚本传参与接参2.3.vars2.4.props2.5.prev 3.常用脚本3.1.MD5加密单个参数&#xff1a;3.2.MD5加密多个参数&#xff1a;3.3.URLe…

【MongoDB】MongoDB的聚合(Aggregate、Map Reduce)与管道(Pipline) 及索引详解(附详细案例)

文章目录 MongoDB的聚合操作&#xff08;Aggregate&#xff09;MongoDB的管道&#xff08;Pipline操作&#xff09;MongoDB的聚合&#xff08;Map Reduce&#xff09;MongoDB的索引 更多相关内容可查看 MongoDB的聚合操作&#xff08;Aggregate&#xff09; 简单理解&#xff…

快速了解SpringBoot 统一功能处理

拦截器 什么是拦截器&#xff1a; 拦截器是Spring框架提供的重要功能之一&#xff0c;主要进行拦截用户请求&#xff0c;在指定方法前后&#xff0c;根据业务需求&#xff0c;执行预先设定的代码。 也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以…

百兆网络变压器在无人机系统起到什么作用

华强盛电子 导读&#xff1a; 百兆网络变压器&#xff08;通常指的是100M Ethernet Transformer&#xff09;在无人机系统中扮演着重要的角色&#xff0c;尤其是在网络通信和电气隔离方面 1.电气隔离 网络变压器通过提供电气隔离&#xff0c;帮助保护无人机的电子设备免受电流…

在双显示器环境中利用Sunshine与Moonlight实现游戏串流的同时与电脑其他任务互不干扰

我和老婆经常会同时需要操作家里的电脑&#xff0c;在周末老婆有时要用电脑加班上网办公&#xff0c;而我想在难得的周末好好地Game一下&#xff08;在客厅用电视机或者平板串流&#xff09;&#xff0c;但是电脑只有一个&#xff0c;以往我一直都是把电脑让给老婆&#xff0c;…