2024年最新IdleStateHandler 心跳机制源码详解,源码解读-别再说你不知道HashMap原理

还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!

王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。

对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!

【完整版领取方式在文末!!】

93道网络安全面试题

内容实在太多,不一一截图了

黑客学习资源推荐

最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

😝朋友们如果有需要的话,可以联系领取~

1️⃣零基础入门
① 学习路线

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

image

② 路线对应学习视频

同时每个成长路线对应的板块都有配套的视频提供:

image-20231025112050764

2️⃣视频配套工具&国内外网安书籍、文档
① 工具

② 视频

image1

③ 书籍

image2

资源较为敏感,未展示全面,需要的最下面获取

在这里插入图片描述在这里插入图片描述

② 简历模板

在这里插入图片描述

因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

    if (this.readerIdleTimeNanos > 0L) {
        // 这里的 schedule 方法会调用 eventLoop 的 schedule 方法,将定时任务添加进队列中
        this.readerIdleTimeout = this.schedule(ctx, new IdleStateHandler.ReaderIdleTimeoutTask(ctx), this.readerIdleTimeNanos, TimeUnit.NANOSECONDS);
    }

    if (this.writerIdleTimeNanos > 0L) {
        this.writerIdleTimeout = this.schedule(ctx, new IdleStateHandler.WriterIdleTimeoutTask(ctx), this.writerIdleTimeNanos, TimeUnit.NANOSECONDS);
    }

    if (this.allIdleTimeNanos > 0L) {
        this.allIdleTimeout = this.schedule(ctx, new IdleStateHandler.AllIdleTimeoutTask(ctx), this.allIdleTimeNanos, TimeUnit.NANOSECONDS);
    }

}

}


定时任务添加到对应线程`EventLoopExecutor`对应的任务队列`taskQueue`中,在对应线程的`run()`方法中循环执行。只要给定的参数大于`0`,就创建一个定时任务,每个事件都创建。同时,将`state`状态设置为`1`,防止重复初始化。调用`initOutputChanged`方法,初始化**监控出站数据属性**



private void initOutputChanged(ChannelHandlerContext ctx) {
if (this.observeOutput) {
Channel channel = ctx.channel();
Unsafe unsafe = channel.unsafe();
ChannelOutboundBuffer buf = unsafe.outboundBuffer();
// 记录了出站缓冲区相关的数据,buf 对象的 hash 码,和 buf 的剩余缓冲字节数
if (buf != null) {
this.lastMessageHashCode = System.identityHashCode(buf.current());
this.lastPendingWriteBytes = buf.totalPendingWriteBytes();
this.lastFlushProgress = buf.currentProgress();
}
}
}


这边会触发一个`ReaderIdleTimeoutTask`:`nextDelay`的初始化值为超时秒数`readerIdleTimeNanos`。如果检测的时候没有正在读,且计算多久没读了,`nextDelay` -= 当前时间 - 上次读取时间,假如这个结果是`6s`,说明最后一次调用`channelRead`已经是`6s`之前的事情了,你设置的是`5s`,那么`nextDelay`则为`-1`,说明超时了。则创建`IdleStateEvent`事件,`IdleState`枚举值为`READER_IDLE`,然后调用`channelIdle`方法分发给下一个`ChannelInboundHandler`,通常由用户自定义一个`ChannelInboundHandler`来捕获并处理。



private final class ReaderIdleTimeoutTask extends IdleStateHandler.AbstractIdleTask {
ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
}

protected void run(ChannelHandlerContext ctx) {
    long nextDelay = IdleStateHandler.this.readerIdleTimeNanos;
    if (!IdleStateHandler.this.reading) {
        nextDelay -= IdleStateHandler.this.ticksInNanos() - IdleStateHandler.this.lastReadTime;
    }

    if (nextDelay <= 0L) {
        IdleStateHandler.this.readerIdleTimeout = IdleStateHandler.this.schedule(ctx, this, IdleStateHandler.this.readerIdleTimeNanos, TimeUnit.NANOSECONDS);
        boolean first = IdleStateHandler.this.firstReaderIdleEvent;
        IdleStateHandler.this.firstReaderIdleEvent = false;

        try {
            IdleStateEvent event = IdleStateHandler.this.newIdleStateEvent(IdleState.READER\_IDLE, first);
            IdleStateHandler.this.channelIdle(ctx, event);
        } catch (Throwable var6) {
            ctx.fireExceptionCaught(var6);
        }
    } else {
        IdleStateHandler.this.readerIdleTimeout = IdleStateHandler.this.schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
    }

}

}


`firstxxxxIdleEvent`作用:假设当你的客户端应用每次接收数据是`30`秒,而你的写空闲时间是`25`秒,那么,当你数据还没有写出的时候,写空闲时间触发了。实际上是不合乎逻辑的。因为你的应用根本不空闲。


`Netty`的解决方案是:记录最后一次输出消息的相关信息,并使用一个值`firstXXXXIdleEvent`表示是否再次活动过,每次读写活动都会将对应的`first`值更新为`true`,如果是`false`,说明这段时间没有发生过读写事件。同时如果第一次记录出站的相关数据和第二次得到的出站相关数据不同,则说明数据在缓慢的出站,就不用触发空闲事件。


总的来说,这个字段就是用来对付 “客户端接收数据奇慢无比,慢到比空闲时间还多” 的极端情况。所以,`Netty`默认是关闭这个字段的。


总的来说,每次读取操作都会记录一个时间,定时任务时间到了,会计算当前时间和最后一次读的时间的间隔,如果间隔超过了设置的时间,就触发·UserEventTriggered`方法:



protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
ctx.fireUserEventTriggered(evt);
}


【3】写任务的`run`方法逻辑基本和读任务的逻辑一样,唯一不同的就是有一个针对 出站较慢数据的判断。



if (hasOutputChanged(ctx, first)) {
return;
}


如果这个方法返回`true`,就不执行触发事件操作了,即使时间到了。看看该方法实现:



private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
if (observeOutput) {
// 如果最后一次写的时间和上一次记录的时间不一样,说明写操作进行过了,则更新此值
if (lastChangeCheckTimeStamp != lastWriteTime) {
lastChangeCheckTimeStamp = lastWriteTime;
// 但如果,在这个方法的调用间隙修改的,就仍然不触发事件
if (!first) { // #firstWriterIdleEvent or #firstAllIdleEvent
return true;
}
}
Channel channel = ctx.channel();
Unsafe unsafe = channel.unsafe();
ChannelOutboundBuffer buf = unsafe.outboundBuffer();
// 如果出站区有数据
if (buf != null) {
// 拿到出站缓冲区的 对象 hashcode
int messageHashCode = System.identityHashCode(buf.current());
// 拿到这个 缓冲区的 所有字节
long pendingWriteBytes = buf.totalPendingWriteBytes();
// 如果和之前的不相等,或者字节数不同,说明,输出有变化,将 “最后一个缓冲区引用” 和 “剩余字节数” 刷新
if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
lastMessageHashCode = messageHashCode;
lastPendingWriteBytes = pendingWriteBytes;
// 如果写操作没有进行过,则任务写的慢,不触发空闲事件
if (!first) {
return true;
}
}
}
}
return false;
}


如果用户没有设置需要观察出站情况。就返回`false`,继续执行事件。如果设置了观察出站的情况,且最后一次写的时间和上一次记录的时间不一样,说明写操作刚刚做过了,则更新此值,但仍然需要判断这个`first`的值,如果这个值还是`false`,说明在这个写事件是在两个方法调用间隙完成的,或者是第一次访问这个方法,就仍然不触发事件。如果不满足上面的条件,就取出缓冲区对象,如果缓冲区没对象了,说明没有发生写的很慢的事件,就触发空闲事件。反之,记录当前缓冲区对象的`hashcode`和剩余字节数,再和之前的比较,如果任意一个不相等,说明数据在变化,或者说数据在慢慢的写出去。那么就更新这两个值,留在下一次判断。继续判断`first`,如果是`fasle`,说明这是第二次调用,就不用触发空闲事件了。


![](https://img-blog.csdnimg.cn/direct/166fe4907c0b4e28b4dfcdb920f58444.png)


【4】所有事件的`run`方法:这个类叫做`AllIdleTimeoutTask`,表示这个监控着所有的事件。当读写事件发生时,都会记录。代码逻辑和写事件的的基本一致,除了这里:



long nextDelay = allIdleTimeNanos;
if (!reading) {
// 当前时间减去 最后一次写或读 的时间 ,若大于0,说明超时了
nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
}


这里的时间计算是取读写事件中的最大值来的。然后像写事件一样,判断是否发生了写的慢的情况。最后调用`ctx.fireUserEventTriggered(evt)`方法。


通常这个使用的是最多的。构造方法一般是:



pipeline.addLast(new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS));


读写都是`0`表示禁用,`30`表示`30`秒内没有任务读写事件发生,就触发事件。注意,当不是`0`的时候,这三个任务会重叠。


### 四、总结


【1】`IdleStateHandler`心跳检测主要是通过向线程任务队列中添加定时任务,判断`channelRead()`方法或`write()`方法是否调用空闲超时,如果超时则触发超时事件执行自定义`userEventTrigger()`方法;  
 【2】`Netty`通过`IdleStateHandler`实现最常见的心跳机制不是一种双向心跳的`PING-PONG`模式,而是客户端发送心跳数据包,服务端接收心跳但不回复,因为如果服务端同时有上千个连接,心跳的回复需要消耗大量网络资源;如果服务端一段时间内没有收到客户端的心跳数据包则认为客户端已经下线,将通道关闭避免资源的浪费;在这种心跳模式下服务端可以感知客户端的存活情况,无论是宕机的正常下线还是网络问题的非正常下线,服务端都能感知到,而客户端不能感知到服务端的非正常下线;  
 【3】要想实现客户端感知服务端的存活情况,需要进行双向的心跳;`Netty`中的`channelInactive()`方法是通过`Socket`连接关闭时挥手数据包触发的,因此可以通过`channelInactive()`方法感知正常的下线情况,但是因为网络异常等非正常下线则无法感知;







还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!


王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。


对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!


【完整版领取方式在文末!!】


***93道网络安全面试题***


![](https://i-blog.csdnimg.cn/blog_migrate/e8e8e04f5f05fdeb34634b0b3780bd33.png)








![](https://i-blog.csdnimg.cn/blog_migrate/99d4e30cb19514a36725fd71dd8b3308.png)





![](https://i-blog.csdnimg.cn/blog_migrate/79019216a46e2955b8c154ad933e9c8c.png)





内容实在太多,不一一截图了


### 黑客学习资源推荐


最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!


对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

#### 1️⃣零基础入门


##### ① 学习路线


对于从来没有接触过网络安全的同学,我们帮你准备了详细的**学习成长路线图**。可以说是**最科学最系统的学习路线**,大家跟着这个大的方向学习准没问题。


![image](https://i-blog.csdnimg.cn/blog_migrate/9e5c1ca69284d5344a87a1e01fe18208.gif#pic_center)


##### ② 路线对应学习视频


同时每个成长路线对应的板块都有配套的视频提供:


![image-20231025112050764](https://i-blog.csdnimg.cn/blog_migrate/b84394276be4f7fcf4d176fa628748ac.png#pic_center)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值