IdleStateHandler是Netty为我们提供的检测连接有效性的处理器,一共有读空闲,写空闲,读/写空闲三种监测机制。
将其添加到我们的ChannelPipline中,便可以用来检测空闲。
先通过一段代码来学习下IdleStateHandler的用法:
ConnectStateHandler:(负责监测通道的各种状态并处理空闲事件IdleStateEvent)
1 package com.insaneXs.netty.idlestate;
2
3 import io.netty.channel.ChannelHandlerContext;
4 import io.netty.channel.ChannelInboundHandlerAdapter;
5 import io.netty.handler.timeout.IdleState;
6 import io.netty.handler.timeout.IdleStateEvent;
7
8 public class ConnectStateHandler extends ChannelInboundHandlerAdapter {
9
10 public ConnectStateHandler() {
11 super();
12 }
13
14 @Override
15 public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
16 System.out.println("Channel Register");
17 super.channelRegistered(ctx);
18 }
19
20 @Override
21 public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
22 System.out.println("Channel Unregister");
23 super.channelUnregistered(ctx);
24 }
25
26 @Override
27 public void channelActive(ChannelHandlerContext ctx) throws Exception {
28 System.out.println("Channel Active");
29 super.channelActive(ctx);
30 }
31
32 @Override
33 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
34 System.out.println("Channel Inactive");
35 super.channelInactive(ctx);
36 }
37
38 @Override
39 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
40 System.out.println("Channel Read");
41 super.channelRead(ctx, msg);
42 }
43
44 @Override
45 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
46 System.out.println("Channel Read Complete");
47 super.channelReadComplete(ctx);
48 }
49
50 @Override
51 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
52 if(evt instanceof IdleStateEvent){
53 if(((IdleStateEvent)evt).state().equals(IdleState.READER_IDLE)){
54 System.out.println("Read Idle");
55 ctx.close();
56 }
57 }else{
58 super.userEventTriggered(ctx, evt);
59 }
60 }
61
62 @Override
63 public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
64 super.channelWritabilityChanged(ctx);
65 }
66
67 @Override
68 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
69 super.exceptionCaught(ctx, cause);
70 }
71 }
服务器代码:
1 package com.insaneXs.netty.idlestate;
2
3 import io.netty.bootstrap.ServerBootstrap;
4 import io.netty.channel.ChannelInitializer;
5 import io.netty.channel.ChannelPipeline;
6 import io.netty.channel.EventLoopGroup;
7 import io.netty.channel.nio.NioEventLoopGroup;
8 import io.netty.channel.socket.nio.NioServerSocketChannel;
9 import io.netty.channel.socket.nio.NioSocketChannel;
10 import io.netty.handler.timeout.IdleStateHandler;
11
12 public class NettyServer {
13
14 public static void main(String[] args){
15 EventLoopGroup boss = new NioEventLoopGroup(1);
16 EventLoopGroup work = new NioEventLoopGroup(4);
17
18 try {
19 ServerBootstrap bootstrap = new ServerBootstrap();
20 bootstrap.group(boss,work)
21 .channel(NioServerSocketChannel.class)
22 .childHandler(new ChannelInitializer<NioSocketChannel>() {
23 @Override
24 protected void initChannel(NioSocketChannel ch) throws Exception {
25 ChannelPipeline pipeline = ch.pipeline();
26
27 pipeline.addLast(new IdleStateHandler(30, 0, 0));
28 pipeline.addLast(new ConnectStateHandler());
29 }
30 });
31
32
33 bootstrap.bind(8322).sync().channel().closeFuture().sync();
34 } catch (InterruptedException e) {
35 e.printStackTrace();
36 }
37 }
38 }
测试客户端代码:
1 package com.insaneXs.netty.common;
2
3 import io.netty.bootstrap.Bootstrap;
4 import io.netty.channel.ChannelInboundHandlerAdapter;
5 import io.netty.channel.EventLoopGroup;
6 import io.netty.channel.nio.NioEventLoopGroup;
7 import io.netty.channel.socket.nio.NioSocketChannel;
8
9 public class CommonNettyClient {
10
11 public static void main(String[] args){
12 EventLoopGroup group = new NioEventLoopGroup();
13
14 Bootstrap bootstrap = new Bootstrap();
15 bootstrap.group(group)
16 .channel(NioSocketChannel.class)
17 .remoteAddress("localhost", 8322)
18 .handler(new ChannelInboundHandlerAdapter());
19
20 bootstrap.connect();
21 }
22 }
测试结果:

从上面的输出中我们可以看到Channel的状态变化:
1.连接建立时会从register -> active,
2.当读空闲时,我们产生了一个空闲事件,当ConnectStateHandler捕获这个事件后,会主动断开连接。
3.断开时则是从inactive -> unregister。
接下来我们学习下IdleStateHandler源码:
1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.timeout;
17
18 import io.netty.bootstrap.ServerBootstrap;
19 import io.netty.channel.Channel;
20 import io.netty.channel.Channel.Unsafe;
21 import io.netty.channel.ChannelDuplexHandler;
22 import io.netty.channel.ChannelFuture;
23 import io.netty.channel.ChannelFutureListener;
24 import io.netty.channel.ChannelHandlerContext;
25 import io.netty.channel.ChannelInitializer;
26 import io.netty.channel.ChannelOutboundBuffer;
27 import io.netty.channel.ChannelPromise;
28
29 import java.util.concurrent.ScheduledFuture;
30 import java.util.concurrent.TimeUnit;
31
32 /
33 *
34 * @see ReadTimeoutHandler
35 * @see WriteTimeoutHandler
36 */
37 public class IdleStateHandler extends ChannelDuplexHandler {
38 private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
39
40 // Not create a new ChannelFutureListener per write operation to reduce GC pressure.
41 private final ChannelFutureListener writeListener = new ChannelFutureListener() {
42 @Override
43 public void operationComplete(ChannelFuture future) throws Exception {
44 lastWriteTime = ticksInNanos();
45 firstWriterIdleEvent = firstAllIdleEvent = true;
46 }
47 };
48
49 //是否观察出站情况;默认false
50 private final boolean observeOutput;
51
52 /*******三种超时情况管理*********/
53 //读超时时间
54 private final long readerIdleTimeNanos;
55 //写超时时间
56 private final long writerIdleTimeNanos;
57 //读或写超时时间
58 private final long allIdleTimeNanos;
59
60 //读空闲定时任务,验证是否读超时
61 private ScheduledFuture<?> readerIdleTimeout;
62 //最后一次读时间
63 private long lastReadTime;
64 //是否第一次读超时
65 private boolean firstReaderIdleEvent = true;
66
67 //写空闲定时任务,验证是否写超时
68 private ScheduledFuture<?> writerIdleTimeout;
69 //最后一次写超时
70 private long lastWriteTime;
71 //是否第一次写超时
72 private boolean firstWriterIdleEvent = true;
73
74 //读或写超时定时任务
75 private ScheduledFuture<?> allIdleTimeout;
76 //是否读或写超时
77 private boolean firstAllIdleEvent = true;
78
79 //处理器状态: 0-无状态, 1-初始化, 2-销毁
80 private byte state; // 0 - none, 1 - initialized, 2 - destroyed
81 //读状态标志
82 private boolean reading;
83
84 /****用于观察输出情况*****/
85 private long lastChangeCheckTimeStamp;
86 private int lastMessageHashCode;
87 private long lastPendingWriteBytes;
88
89 //设置超时时间;默认单位为秒
90 public IdleStateHandler(
91 int readerIdleTimeSeconds,
92 int writerIdleTimeSeconds,
93 int allIdleTimeSeconds) {
94
95 this(readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds,
96 TimeUnit.SECONDS);
97 }
98
99 //设置三种情况超时时间与时间单位
100 public IdleStateHandler(
101 long readerIdleTime, long writerIdleTime, long allIdleTime,
102 TimeUnit unit) {
103 this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
104 }
105
106 //设置是否观察输出情况,三种情况超时时间以及时间单位
107 public IdleStateHandler(boolean observeOutput,
108 long readerIdleTime, long writerIdleTime, long allIdleTime,
109 TimeUnit unit) {
110 if (unit == null) {
111 throw new NullPointerException("unit");
112 }
113
114 this.observeOutput = observeOutput;
115
116 if (readerIdleTime <= 0) {
117 readerIdleTimeNanos = 0;
118 } else {
119 readerIdleTimeNanos = Math.max(unit.toNanos(readerIdleTime), MIN_TIMEOUT_NANOS);
120 }
121 if (writerIdleTime <= 0) {
122 writerIdleTimeNanos = 0;
123 } else {
124 writerIdleTimeNanos = Math.max(unit.toNanos(writerIdleTime), MIN_TIMEOUT_NANOS);
125 }
126 if (allIdleTime <= 0) {
127 allIdleTimeNanos = 0;
128 } else {
129 allIdleTimeNanos = Math.max(unit.toNanos(allIdleTime), MIN_TIMEOUT_NANOS);
130 }
131 }
132
133 /************将时间转换成毫秒级***************/
134 public long getReaderIdleTimeInMillis() {
135 return TimeUnit.NANOSECONDS.toMillis(readerIdleTimeNanos);
136 }
137
138
139 public long getWriterIdleTimeInMillis() {
140 return TimeUnit.NANOSECONDS.toMillis(writerIdleTimeNanos);
141 }
142
143
144 public long getAllIdleTimeInMillis() {
145 return TimeUnit.NANOSECONDS.toMillis(allIdleTimeNanos);
146 }
147
148 //当处理器被添加时,视情况决定是否初始化
149 @Override
150 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
151 if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
152 //判断channel是否已经激活和注册,避免
153 initialize(ctx);
154 } else {
155 // channelActive() event has not been fired yet. this.channelActive() will be invoked
156 // and initialization will occur there.
157 }
158 }
159
160 //移除时,调用destroy销毁
161 @Override
162 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
163 destroy();
164 }
165
166 //当通道被注册时,视情况决定是否初始化
167 @Override
168 public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
169 // Initialize early if channel is active already.
170 if (ctx.channel().isActive()) {
171 initialize(ctx);
172 }
173 super.channelRegistered(ctx);
174 }
175
176 //当通道激活时,初始化
177 @Override
178 public void channelActive(ChannelHandlerContext ctx) throws Exception {
179 // This method will be invoked only if this handler was added
180 // before channelActive() event is fired. If a user adds this handler
181 // after the channelActive() event, initialize() will be called by beforeAdd().
182 initialize(ctx);
183 super.channelActive(ctx);
184 }
185
186 //通道不活跃时,调用destroy销毁
187 @Override
188 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
189 destroy();
190 super.channelInactive(ctx);
191 }
192
193 //读事件
194 @Override
195 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
196 //开启了读空闲监测 或 读写空闲检测
197 if (readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
198 //修改为正在读,并重置首次读写事件的标志位为true
199 reading = true;
200 firstReaderIdleEvent = firstAllIdleEvent = true;
201 }
202 ctx.fireChannelRead(msg);
203 }
204
205 //读完成
206 @Override
207 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
208 //如果开启了 读/读写标志 且 正在读
209 if ((readerIdleTimeNanos > 0 || allIdleTimeNanos > 0) && reading) {
210 //修改最后一次读取时间,重置正在读标识
211 lastReadTime = ticksInNanos();
212 reading = false;
213 }
214 ctx.fireChannelReadComplete();
215 }
216
217 @Override
218 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
219 //如果开启了 写/读写标志
220 if (writerIdleTimeNanos > 0 || allIdleTimeNanos > 0) {
221 ctx.write(msg, promise.unvoid()).addListener(writeListener);
222 } else {
223 ctx.write(msg, promise);
224 }
225 }
226
227 //初始化
228 private void initialize(ChannelHandlerContext ctx) {
229 //判断状态避免重复初始化
230 switch (state) {
231 case 1:
232 case 2:
233 return;
234 }
235
236 state = 1;
237 //观察输出情况
238 initOutputChanged(ctx);
239
240 //初始化最后一次读写时间
241 lastReadTime = lastWriteTime = ticksInNanos();
242
243 //根据超时时间,判断是否开启超时监测
244 if (readerIdleTimeNanos > 0) {
245 readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
246 readerIdleTimeNanos, TimeUnit.NANOSECONDS);
247 }
248 if (writerIdleTimeNanos > 0) {
249 writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
250 writerIdleTimeNanos, TimeUnit.NANOSECONDS);
251 }
252 if (allIdleTimeNanos > 0) {
253 allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
254 allIdleTimeNanos, TimeUnit.NANOSECONDS);
255 }
256 }
257
258 /**
259 * This method is visible for testing!
260 */
261 long ticksInNanos() {
262 return System.nanoTime();
263 }
264
265 /**
266 * This method is visible for testing!
267 */
268 ScheduledFuture<?> schedule(ChannelHandlerContext ctx, Runnable task, long delay, TimeUnit unit) {
269 return ctx.executor().schedule(task, delay, unit);
270 }
271
272 //销毁
273 private void destroy() {
274 //更改状态 取消定时器
275 state = 2;
276
277 if (readerIdleTimeout != null) {
278 readerIdleTimeout.cancel(false);
279 readerIdleTimeout = null;
280 }
281 if (writerIdleTimeout != null) {
282 writerIdleTimeout.cancel(false);
283 writerIdleTimeout = null;
284 }
285 if (allIdleTimeout != null) {
286 allIdleTimeout.cancel(false);
287 allIdleTimeout = null;
288 }
289 }
290
291 /**
292 * Is called when an {@link IdleStateEvent} should be fired. This implementation calls
293 * {@link ChannelHandlerContext#fireUserEventTriggered(Object)}.
294 */
295 protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
296 ctx.fireUserEventTriggered(evt);
297 }
298
299 /**
300 * Returns a {@link IdleStateEvent}.
301 */
302 protected IdleStateEvent newIdleStateEvent(IdleState state, boolean first) {
303 switch (state) {
304 case ALL_IDLE:
305 return first ? IdleStateEvent.FIRST_ALL_IDLE_STATE_EVENT : IdleStateEvent.ALL_IDLE_STATE_EVENT;
306 case READER_IDLE:
307 return first ? IdleStateEvent.FIRST_READER_IDLE_STATE_EVENT : IdleStateEvent.READER_IDLE_STATE_EVENT;
308 case WRITER_IDLE:
309 return first ? IdleStateEvent.FIRST_WRITER_IDLE_STATE_EVENT : IdleStateEvent.WRITER_IDLE_STATE_EVENT;
310 default:
311 throw new IllegalArgumentException("Unhandled: state=" + state + ", first=" + first);
312 }
313 }
314
315 /**
316 * @see #hasOutputChanged(ChannelHandlerContext, boolean)
317 */
318 private void initOutputChanged(ChannelHandlerContext ctx) {
319 if (observeOutput) {
320 Channel channel = ctx.channel();
321 Unsafe unsafe = channel.unsafe();
322 ChannelOutboundBuffer buf = unsafe.outboundBuffer();
323 //初始化消息内容的HashCode和byte
324 if (buf != null) {
325 lastMessageHashCode = System.identityHashCode(buf.current());
326 lastPendingWriteBytes = buf.totalPendingWriteBytes();
327 }
328 }
329 }
330
331 //判断输出是否有变化
332 private boolean hasOutputChanged(ChannelHandlerContext ctx, boolean first) {
333 if (observeOutput) {
334
335 // We can take this shortcut if the ChannelPromises that got passed into write()
336 // appear to complete. It indicates "change" on message level and we simply assume
337 // that there's change happening on byte level. If the user doesn't observe channel
338 // writability events then they'll eventually OOME and there's clearly a different
339 // problem and idleness is least of their concerns.
340 if (lastChangeCheckTimeStamp != lastWriteTime) {
341 lastChangeCheckTimeStamp = lastWriteTime;
342
343 // But this applies only if it's the non-first call.
344 if (!first) {
345 return true;
346 }
347 }
348
349 Channel channel = ctx.channel();
350 Unsafe unsafe = channel.unsafe();
351 ChannelOutboundBuffer buf = unsafe.outboundBuffer();
352
353 if (buf != null) {
354 int messageHashCode = System.identityHashCode(buf.current());
355 long pendingWriteBytes = buf.totalPendingWriteBytes();
356
357 if (messageHashCode != lastMessageHashCode || pendingWriteBytes != lastPendingWriteBytes) {
358 lastMessageHashCode = messageHashCode;
359 lastPendingWriteBytes = pendingWriteBytes;
360
361 if (!first) {
362 return true;
363 }
364 }
365 }
366 }
367
368 return false;
369 }
370
371 //监测任务的父类
372 private abstract static class AbstractIdleTask implements Runnable {
373
374 private final ChannelHandlerContext ctx;
375
376 AbstractIdleTask(ChannelHandlerContext ctx) {
377 this.ctx = ctx;
378 }
379
380 @Override
381 public void run() {
382 if (!ctx.channel().isOpen()) {
383 return;
384 }
385
386 run(ctx);
387 }
388
389 protected abstract void run(ChannelHandlerContext ctx);
390 }
391
392 //读监测定时任务
393 private final class ReaderIdleTimeoutTask extends AbstractIdleTask {
394
395 ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
396 super(ctx);
397 }
398
399 @Override
400 protected void run(ChannelHandlerContext ctx) {
401 long nextDelay = readerIdleTimeNanos;
402 if (!reading) {//不在读的过程中
403 //计算是否超时
404 nextDelay -= ticksInNanos() - lastReadTime;
405 }
406
407 if (nextDelay <= 0) {//已经超时
408 //下次空闲监测
409 readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);
410
411 boolean first = firstReaderIdleEvent;
412 firstReaderIdleEvent = false;
413
414 try {
415 //出发读超时事件
416 IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
417 channelIdle(ctx, event);
418 } catch (Throwable t) {
419 ctx.fireExceptionCaught(t);
420 }
421 } else {
422 // Read occurred before the timeout - set a new timeout with shorter delay.
423 readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
424 }
425 }
426 }
427
428 //写监测定时任务
429 private final class WriterIdleTimeoutTask extends AbstractIdleTask {
430
431 WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
432 super(ctx);
433 }
434
435 @Override
436 protected void run(ChannelHandlerContext ctx) {
437
438 long lastWriteTime = IdleStateHandler.this.lastWriteTime;
439 long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
440 if (nextDelay <= 0) {//写超时
441 //下一次超时检查时间
442 writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS);
443
444 boolean first = firstWriterIdleEvent;
445 firstWriterIdleEvent = false;
446
447 try {
448 //输入内容是否发生变化:观察输出模式下且输出内容发生变化则不认为写超时
449 if (hasOutputChanged(ctx, first)) {
450 return;
451 }
452 //传递写超时事件
453 IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
454 channelIdle(ctx, event);
455 } catch (Throwable t) {
456 ctx.fireExceptionCaught(t);
457 }
458 } else {
459 // Write occurred before the timeout - set a new timeout with shorter delay.
460 writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
461 }
462 }
463 }
464
465 //读写监测定时任务
466 private final class AllIdleTimeoutTask extends AbstractIdleTask {
467
468 AllIdleTimeoutTask(ChannelHandlerContext ctx) {
469 super(ctx);
470 }
471
472 @Override
473 protected void run(ChannelHandlerContext ctx) {
474
475 long nextDelay = allIdleTimeNanos;
476 if (!reading) {
477 nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
478 }
479 if (nextDelay <= 0) {
480 // Both reader and writer are idle - set a new timeout and
481 // notify the callback.
482 allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS);
483
484 boolean first = firstAllIdleEvent;
485 firstAllIdleEvent = false;
486
487 try {
488 if (hasOutputChanged(ctx, first)) {
489 return;
490 }
491
492 IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
493 channelIdle(ctx, event);
494 } catch (Throwable t) {
495 ctx.fireExceptionCaught(t);
496 }
497 } else {
498 // Either read or write occurred before the timeout - set a new
499 // timeout with shorter delay.
500 allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
501 }
502 }
503 }
504 }
代码的关键部分都已经给出了注释。
这里主要关注几个问题,带着这些问题去思考代码设计的目的:
1.代码中firstXXXIdleEvent的标志位的作用是什么?
2.ObserveOutput标志位的作用是什么?
3.reading的标志的作用是什么?
4.lastReadTime和lastWriteTime会在什么时候被重置?
首先来回答第一个问题,为什么会有firstXXXIdleEvent的标识。
这是因为一次读写事件中,可能会产生多次的空闲超时事件。比如当我设置写空闲时间为5秒,而在某些情况下,我整个写过程需要30秒。那么这一次写过程就会产生多个写空闲事件。firstXXXIdleEvent标志位就是用来表明此次空闲事件是否为第一次空闲事件。
知道了第一个问题后,我们在看ObserveOutput标志的作用。ObserveOutput标志用来解决上述的慢输出的问题。如果设置为true,那么即使写过程中发生了写空闲事件,但是只要hasOutputChanged方法判断此时仍然在向外写(写缓存发生变化),那么就不会为此次超时产生写超时事件。
那么reading标志的作用是什么?reading的标志是在read方法开始时,被设置为true,在readComplete方法中又被设置为false。也就是reading标志表示正在发生读的过程。
最后的问题,lastReadTime和lastWriteTime在什么时候被重置?lastReadTime和lastWriteTime会在相应的定时器中被用来和空闲时间作比较,以此来检测在这段空闲时间中,是否发生过完整的读或写过程。因此,它们在handler初始化时被初始化值。lastReadTime会在readComplete时更新值。而lastWriteTime则是在WriterListener中设置(写过程完成后的回掉中)。
来源:https://www.cnblogs.com/insaneXs/p/9776164.html