当前位置:   article > 正文

Netty服务端源码阅读笔记(十)IdleStateHandler_new idlestatehandler

new idlestatehandler

目录

构造函数

初始化

记录最后一次读写时间

定时检测读写空闲时间并发布事件

测试observeOutput


 

继承复合handler,既是出栈处理器,又是入栈处理器。

工作流程:

1)初始化new时定义了读写的空闲超时时间

2)在handler的读写channelRead和write方法中记录本次读写的时间

3)启动了三个定时任务,定时检测,任务中如果检测到当前时间距离上次读写时间超过了定义的空闲时间,则发布一个IdleStateEvent事件,调用handler链上的UserEventTriggered方法

所以可以自定义handler实现ChannelInboundHandler中的userEventTriggered方法,进行自定义处理,关闭channel或发送心跳请求

  1. public class IdleStateHandler extends ChannelDuplexHandler {
  2. }

构造函数

共五个参数

observeOutput 写超时是在handler的write方法记录最后一次的写时间,如果这个参数为false,那么只会检测这个write方法记录的时间与当前时间差是否超过定义的空闲时间。但是如果是发送大数据包,发送的耗时时间超过了定义的空闲时间,也会触发事件。参数设置为true的话,不光检测write方法记录的时间,也会检测当前channel的发送缓冲区是否有变化,有变化表示数据正在发送中或者有新的write写入,不触发写空闲超时

每一次的write(ByteBuf) 方法会将此次的byteBuf写入到发送缓冲区,发送缓冲区将btyeBuf以一个链表存放,记录所有节点的一个总的数据大小size,调用flush()时,会循环链表节点,写入到socket,每次写完,会从总size中减去此次已写入的字节数。当然,flush()发送中要是来了新的write(buf),也会更新链表和总大小size。

本文最后会在源码上加入输入语句测试这个参数。

 

readerIdleTime 读空闲超时

writerIdleTime 写空闲时间

allIdleTime  读写空闲时间

unit 时间单位

  1. public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {
  2. this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
  3. TimeUnit.SECONDS);
  4. }
  5. public IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime,
  6. TimeUnit unit) {
  7. this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
  8. }
  9. public IdleStateHandler(boolean observeOutput, long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit) {
  10. if (unit == null) {
  11. throw new NullPointerException("unit");
  12. }
  13. this.observeOutput = observeOutput;
  14. if (readerIdleTime <= 0) {
  15. readerIdleTimeNanos = 0;
  16. } else {
  17. readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
  18. }
  19. if (writerIdleTime <= 0) {
  20. writerIdleTimeNanos = 0;
  21. } else {
  22. writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
  23. }
  24. if (allIdleTime <= 0) {
  25. allIdleTimeNanos = 0;
  26. } else {
  27. allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
  28. }
  29. }

初始化

在handlerAdded、channelActive、channelRegistered,会调用初始化方法,初始化发送缓冲区大小、启动三个定时检测任务

  1. private void initialize(ChannelHandlerContext ctx) {
  2. // Avoid the case where destroy() is called before scheduling timeouts.
  3. // See: https://github.com/netty/netty/issues/143
  4. // 只初始化一次
  5. switch (state) {
  6. case 1:
  7. case 2:
  8. return;
  9. }
  10. state = 1;
  11. // observeOutput上边说的那事,初始化当前channel发送缓冲区的大小
  12. initOutputChanged(ctx);
  13. lastReadTime = lastWriteTime = ticksInNanos();
  14. if (readerIdleTimeNanos > 0) {
  15. readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
  16. readerIdleTimeNanos, TimeUnit.NANOSECONDS);
  17. }
  18. if (writerIdleTimeNanos > 0) {
  19. writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
  20. writerIdleTimeNanos, TimeUnit.NANOSECONDS);
  21. }
  22. if (allIdleTimeNanos > 0) {
  23. allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
  24. allIdleTimeNanos, TimeUnit.NANOSECONDS);
  25. }
  26. }
  27. private void initOutputChanged(ChannelHandlerContext ctx) {
  28. if (observeOutput) {
  29. Channel channel = ctx.channel();
  30. Unsafe unsafe = channel.unsafe();
  31. // 发送缓冲区
  32. ChannelOutboundBuffer buf = unsafe.outboundBuffer();
  33. if (buf != null) {
  34. // 当前的发送缓冲区正在发送的buf和要发送的总大小
  35. lastMessageHashCode = System.identityHashCode(buf.current());
  36. lastPendingWriteBytes = buf.totalPendingWriteBytes();
  37. lastFlushProgress = buf.currentProgress();
  38. }
  39. }
  40. }

记录最后一次读写时间

记录lastReadTime 

  1. @Override
  2. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  3. if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
  4. reading = true;
  5. firstReaderIdleEvent = firstAllIdleEvent = true;
  6. }
  7. ctx.fireChannelRead(msg);
  8. }
  9. @Override
  10. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  11. if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
  12. lastReadTime = ticksInNanos();
  13. reading = false;
  14. }
  15. ctx.fireChannelReadComplete();
  16. }

记录lastWriteTime ,ctx.write设置监听,回调中设置读时间。在每次write完,flush()方法中往socket中写完数据调用ChannelPromise的setSuccess设置成功状态时,会调用其上注册的监听器方法。

  1. private final ChannelFutureListener writeListener = new ChannelFutureListener() {
  2. @Override
  3. public void operationComplete(ChannelFuture future) throws Exception {
  4. lastWriteTime = ticksInNanos();
  5. firstWriterIdleEvent = firstAllIdleEvent = true;
  6. }
  7. };
  8. @Override
  9. public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
  10. // Allow writing with void promise if handler is only configured for read timeout events.
  11. if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
  12. ctx.write(msg, promise.unvoid()).addListener(writeListener);
  13. } else {
  14. ctx.write(msg, promise);
  15. }
  16. }

定时检测读写空闲时间并发布事件

ReaderIdleTimeoutTask 读空闲超时检测任务

  1. private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
  2. ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
  3. super(ctx);
  4. }
  5. @Override
  6. protected void run(ChannelHandlerContext ctx) {
  7. long nextDelay = readerIdleTimeNanos;
  8. // reading变量会在channelRead设置为true,channelReadComplate设置为false
  9. // 如果为true,当前有一波还没读完,nextDelay大于0,直接再次放入定时任务下次再说
  10. if (!reading) {
  11. // 读完了,算当前时间距离上次读多长时间和读空闲超时时间
  12. nextDelay -= ticksInNanos() - lastReadTime;
  13. }
  14. // 小于0表示到位了,要发布事件
  15. if (nextDelay <= 0) {
  16. // Reader is idle - set a new timeout and notify the callback.
  17. // 再定时一个
  18. readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
  19. boolean first = firstReaderIdleEvent;
  20. firstReaderIdleEvent = false;
  21. try {
  22. IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
  23. // 发布READER_IDLE事件,调用链上的UserEventTriggered方法
  24. channelIdle(ctx, event);
  25. } catch (Throwable t) {
  26. ctx.fireExceptionCaught(t);
  27. }
  28. } else {
  29. // Read occurred before the timeout - set a new timeout with shorter delay.
  30. readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
  31. }
  32. }
  33. }

WriterIdleTimeoutTask 写空闲超时检测任务

  1. private final class WriterIdleTimeoutTask extends AbstractIdleTask {
  2. WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
  3. super(ctx);
  4. }
  5. @Override
  6. protected void run(ChannelHandlerContext ctx) {
  7. // 这里和读一样,算时间,如果大于定义的空闲时间则nextDelay 《= 0
  8. long lastWriteTime = IdleStateHandler.this.lastWriteTime;
  9. long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
  10. if (nextDelay <= 0) {
  11. // 重新设置一个定时
  12. writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
  13. // 回调函数执行后firstWriterIdleEvent = true,首次超时不检测缓冲区变化
  14. boolean first = firstWriterIdleEvent;
  15. firstWriterIdleEvent = false;
  16. try {
  17. // 这里就是构造函数中第一个参数observeOutput为true,检测缓冲区是否发生变化,如果变化了,表示正在发送中,就不触发事件直接return
  18. if (hasOutputChanged(ctx, first)) {
  19. return;
  20. }
  21. IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
  22. channelIdle(ctx, event);
  23. } catch (Throwable t) {
  24. ctx.fireExceptionCaught(t);
  25. }
  26. } else {
  27. // Write occurred before the timeout - set a new timeout with shorter delay.
  28. writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
  29. }
  30. }
  31. }
  32. private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
  33. if (observeOutput) {
  34. if (lastChangeCheckTimeStamp != lastWriteTime) {
  35. lastChangeCheckTimeStamp = lastWriteTime;
  36. // 首次不触发超时
  37. if (!first) {
  38. return true;
  39. }
  40. }
  41. // 获取发送缓冲区
  42. Channel channel = ctx.channel();
  43. Unsafe unsafe = channel.unsafe();
  44. ChannelOutboundBuffer buf = unsafe.outboundBuffer();
  45. if (buf != null) {
  46. // 取当前的缓冲区要flush()发送的数据大小
  47. int messageHashCode = System.identityHashCode(buf.current());
  48. // 当前flush()共要发送多少数据,每次发送一些会更新buf的pendingWriteBytes
  49. long pendingWriteBytes = buf.totalPendingWriteBytes();
  50. // 和上次记的不一样,那说明缓冲区变化,,return true不触发事件
  51. if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
  52. lastMessageHashCode = messageHashCode;
  53. lastPendingWriteBytes = pendingWriteBytes;
  54. if (!first) {
  55. return true;
  56. }
  57. }
  58. // 相同,Progress也是个发送过程中记录的变量,当前btyeBuf已写入socket的字节数
  59. long flushProgress = buf.currentProgress();
  60. if (flushProgress != lastFlushProgress) {
  61. lastFlushProgress = flushProgress;
  62. if (!first) {
  63. return true;
  64. }
  65. }
  66. }
  67. }
  68. return false;
  69. }

 

测试observeOutput

我是直接改netty源码测试的,改了IdleStateHandler类中的记录最后一次写时间的回调方法和写空闲检测的定时任务,在触发事件和检测到缓冲区变化的那行加了输出

  1. private final ChannelFutureListener writeListener = new ChannelFutureListener() {
  2. @Override
  3. public void operationComplete(ChannelFuture future) throws Exception {
  4. Date dd=new Date();
  5. //格式化
  6. SimpleDateFormat sim=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  7. String time=sim.format(dd);
  8. System.out.println("检测到写入完成,更新了最后写的时间--" + time);
  9. lastWriteTime = ticksInNanos();
  10. firstWriterIdleEvent = firstAllIdleEvent = true;
  11. }
  12. };
  13. private final class WriterIdleTimeoutTask extends AbstractIdleTask {
  14. WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
  15. super(ctx);
  16. }
  17. @Override
  18. protected void run(ChannelHandlerContext ctx) {
  19. long lastWriteTime = IdleStateHandler.this.lastWriteTime;
  20. long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
  21. if (nextDelay <= 0) {
  22. // Writer is idle - set a new timeout and notify the callback.
  23. writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
  24. boolean first = firstWriterIdleEvent;
  25. firstWriterIdleEvent = false;
  26. Date dd=new Date();
  27. //格式化
  28. SimpleDateFormat sim=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  29. String time=sim.format(dd);
  30. try {
  31. System.out.println("已经超时了,开始判断缓冲区是否变化,是否有新的write或写入完毕的buf---" + time);
  32. if (hasOutputChanged(ctx, first)) {
  33. System.out.println("hasOutputChanged为true缓冲区变化了,不触发写空闲超时---" + time);
  34. return;
  35. }
  36. System.out.println("触发了写空闲超时---" + time);
  37. IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
  38. channelIdle(ctx, event);
  39. } catch (Throwable t) {
  40. ctx.fireExceptionCaught(t);
  41. }
  42. } else {
  43. // Write occurred before the timeout - set a new timeout with shorter delay.
  44. writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
  45. }
  46. }
  47. }
  48. private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
  49. if (observeOutput) {
  50. if (lastChangeCheckTimeStamp != lastWriteTime) {
  51. lastChangeCheckTimeStamp = lastWriteTime;
  52. // But this applies only if it's the non-first call.
  53. if (!first) {
  54. return true;
  55. }
  56. }
  57. Channel channel = ctx.channel();
  58. Unsafe unsafe = channel.unsafe();
  59. ChannelOutboundBuffer buf = unsafe.outboundBuffer();
  60. if (buf != null) {
  61. int messageHashCode = System.identityHashCode(buf.current());
  62. long pendingWriteBytes = buf.totalPendingWriteBytes();
  63. if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
  64. // 这是缓冲区总大小 变化
  65. System.out.println("更新handler中的缓冲区大小数据pendingWriteBytes");
  66. lastMessageHashCode = messageHashCode;
  67. lastPendingWriteBytes = pendingWriteBytes;
  68. if (!first) {
  69. return true;
  70. }
  71. }
  72. long flushProgress = buf.currentProgress();
  73. if (flushProgress != lastFlushProgress) {
  74. // 这是单个消息分块发送的情况,已经发送的数据量
  75. System.out.println("更新handler中的缓冲区大小数据flushProgress");
  76. lastFlushProgress = flushProgress;
  77. if (!first) {
  78. return true;
  79. }
  80. }
  81. }
  82. }
  83. return false;
  84. }

 

测试客户端类

因为跟服务端没太大关系,所以其他代码就不贴了,服务端客户端没有做粘包拆包的handler

5000万长度的字符串,write()1次,然后flush(), 设置的写超时时间为5秒

  1. public class Client3 {
  2. public static void main(String[] args) throws InterruptedException {
  3. NioEventLoopGroup group = new NioEventLoopGroup();
  4. try {
  5. Bootstrap bootstrap = new Bootstrap();
  6. bootstrap.group(group)
  7. .channel(NioSocketChannel.class)
  8. .option(ChannelOption.TCP_NODELAY, true)
  9. .handler(new ChannelInitializer<SocketChannel>() {
  10. @Override
  11. protected void initChannel(SocketChannel ch) throws Exception {
  12. ChannelPipeline pipeline = ch.pipeline();
  13. pipeline.addLast(new IdleStateHandler(false,0, 3000, 0, TimeUnit.MILLISECONDS));
  14. pipeline.addLast(new SomeClientHandler());
  15. }
  16. });
  17. ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
  18. tttt(future.channel());
  19. future.channel().closeFuture().sync();
  20. } finally {
  21. group.shutdownGracefully();
  22. }
  23. }
  24. public static final void tttt(Channel channel) throws InterruptedException {
  25. StringBuilder sb = new StringBuilder();
  26. for (int m = 0; m < 50000000; m++) {
  27. sb.append("t");
  28. }
  29. for (int i = 0; i < 1; i++) {
  30. byte[] bytes = sb.toString().getBytes();
  31. ByteBuf buffer = null;
  32. // 申请缓存空间
  33. buffer = Unpooled.buffer(bytes.length);
  34. // 将数据写入到缓存
  35. buffer.writeBytes(bytes);
  36. // 将缓存中的数据写入到Channel
  37. Date dd=new Date();
  38. //格式化
  39. SimpleDateFormat sim=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  40. String time=sim.format(dd);
  41. System.out.println("开始了write--" + time);
  42. channel.writeAndFlush(buffer);
  43. // TimeUnit.SECONDS.sleep(3);
  44. }
  45. }
  46. }

 

先来看observeOutput为false的执行情况,可以看到,调用flush之后,超时了2次才写完数据触发回调

再来看observeOutput为true的执行情况,首次不检测缓冲区变化,第二次发现flushProgress数据变化了,正在写入状态,所以不触发超时

再将循环改为2次5000万,中间注释去掉加个睡眠,结果因为第二次write改变了缓冲区总大小,所以pendingWriteBytes变化,不触发超时

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/242181
推荐阅读
相关标签
  

闽ICP备14008679号