最近突然得知自己的基础差,与精英的标准相距甚远。的确,平时的工作中,有太多机会允许我运用“基础”以外的东西去完成。让我对“基础”没有很重视,所以决定了马上亡羊补牢。就先从自己最看重的异步编程开刀吧,翻阅起了《Netty权威指南》。大部分内容与看之前的道听途说、自行猜想没差多少。突如其来的如下代码,让我产生了警觉:
代码中,由于手动打开了本地文件,所以在flush操作后,手动关闭了文件。但netty作为一个异步框架,总觉得不太可能需要写出这种同步样式的代码。
我的疑问有两点:1、flush方法返回时,可以确保数据write完毕?2、netty没有提供用于编写收尾逻辑的回调接口?
弄清疑问一:
尝试在互联网上搜索,发现并没有关于这点的描述,所以干脆自己下载了源码自己看。
ChannelHandlerContext接口,对flush的描述:
AbstractChannelHandlerContext抽象类中的具体实现:
意思应该是将pipeline中的ChannelOutboundHandler依次调一遍,参照findContextOutbound的实现:
可以得知,ChannelOutboundHandler是从后向前依次调用的。最后一个调用的为默认的HeadContext:
HeadContext的flush,调用的是DefaultChannelPipeline.channel().unsafe().flush():
以书中的示例代码使用的NioServerSocketChannel为例,当接收到客户端请求时,创建了NioSocketChannel:
NioSocketChannel中unsafe().flush()的调用链为:
unsafe()[继承自:AbstractNioByteChannel、AbstractNioChannel] >>> AbstractChannel.unsafe()
AbstractChannel.unsafe()返回的结果来自:
NioSocketChannel.newUnsafe()
NioSocketChannelUnsafe的flush方法[继承自:NioByteUnsafe、AbstractNioUnsafe、AbstractUnsafe]:
调用的flush0方法:
flush0()[继承自:NioByteUnsafe、AbstractNioUnsafe] >>> AbstractUnsafe.flush0()
调用的NioSocketChannel.doWrite方法:
针对示例中的FileRegion,调用了父类doWrite方法:
其中确实存在针对write未完毕的处理,调用用incompleteWrite(true) >>> setOpWrite():
将SelectionKey.OP_WRITE注册到selector中,并在写操作完成后,由selector再触发一次flush(NioEventLoop代码):
总上,flush方法返回时,不保证数据write完毕。
弄清疑问二:
DefaultFileRegion的API说明:
Default FileRegion implementation which transfer data from a FileChannel or File. Be aware that the FileChannel will be automatically closed once AbstractReferenceCounted.refCnt() returns 0.
DefaultFileRegion类实现了FileRegion接口,继承了AbstractReferenceCounted类。创建DefaultFileRegion实例时可以传入一个FileChannel实现或一个File实例,而且在这里使用FileChannel不需要手动close。
上文的AbstractNioByteChannel.doWrite方法中,完毕时调用了ChannelOutboundBuffer.remove():
这正是用来释放ReferenceCounted对象的逻辑。
DefaultFileRegion中也存在关闭FileChannel的逻辑:
但RandomAccessFile方法中,不只有关闭FileChannel的逻辑:
因此RandomAccessFile.close()方法还是需要手动调用的。
总终方案:
编写DefaultFileRegion子类,重写deallocate方法,额外关闭RandomAccessFile对象。(代码略)
探究感想:
为了论证自己的猜测,确实花费了不少精力。最大的困难在于本次的论点,没有被广大程序员关注,而且错误的代码遍布各种书籍、网站,让人直觉上认为那些就是对的。这正让我联想到之前美团的招聘细则中不要“信中医的”。中医这里代表的是广泛留传下来的传统中医理论知识,不单只运用了现代科学医药手段验证过的中医。中医与软件理论,都十分庞大、且充满糟粕。对待中医和软件知识,都应该持有一种警觉态度,那些被记载、流传下来的手段、方案,真的是正确的吗?适用在你的场景中依然有效?的确,实际运用中会有众多因素,制约着你无法透彻的去验证。但你的知识获取方法,只能是听信他人吗。1753年英国海军军医伦达,经试验得出结论:柠檬汁可用来治疗和预防坏血病。这种的结论,对于航海而言就足够了,不需要知道其根本是维生素C的作用。中医知识通过大量随机双盲对照试验来验证。软件知识,归根到底就是代码,真有什么不明白的,看一看、运行一下。你能了解到什么程度,首先由你想了解到什么程度决定。