写点什么

netty 系列之:netty 中的 Channel 详解

发布于: 31 分钟前

简介 Channel 是连接 ByteBuf 和 Event 的桥梁,netty 中的 Channel 提供了统一的 API,通过这种统一的 API,netty 可以轻松的对接多种传输类型,如 OIO,NIO 等。今天本文将会介绍 Channel 的使用和 Channel 相关的一些概念。


Channel 详解 Channel 是什么? Channel 是一个连接网络输入和 IO 处理的桥梁。你可以通过 Channel 来判断当前的状态,是 open 还是 connected,还可以判断当前 Channel 支持的 IO 操作,还可以使用 ChannelPipeline 对 Channel 中的消息进行处理。


先看下 Channel 的定义:


public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {可以看到 Channel 是一个接口,它继承了 AttributeMap, ChannelOutboundInvoker, Comparable 三个类。Comparable 表示这个类可以用来做比较。AttributeMap 用来存储 Channel 的各种属性。ChannelOutboundInvoker 主要负责 Channel 和外部 SocketAddress 进行连接和对写。


再看下 channel 中定义的方法:


可以看出 channel 中定义的方法是多种多样的,这些方法都有些什么特点呢?接下来一一为您讲解。


异步 IO 和 ChannelFuturenetty 中所有的 IO 都是异步 IO,也就是说所有的 IO 都是立即返回的,返回的时候,IO 可能还没有结束,所以需要返回一个 ChannelFuture,当 IO 有结果之后,会去通知 ChannelFuture,这样就可以取出结果了。


ChannelFuture 是 java.util.concurrent.Future 的子类,它除了可以拿到线程的执行结果之外,还对其进行了扩展,加入了当前任务状态判断、等待任务执行和添加 listener 的功能。


其他的功能都很好理解,它的突破在于可以对 ChannelFuture 添加 listener,我们列出一个添加 listener 的方法:


Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);


添加的 Listener 会在 future 执行结束之后,被通知。不需要自己再去调用 get 等待 future 结束。这里实际上就是异步 IO 概念的实现,不需要主动去调用,当你完成之后来通知我就行。非常的美好!


ChannelFuture 有两个状态:uncompleted 或者 completed,分别代表任务的执行状态。


当一个 IO 刚开始的时候,返回一个 ChannelFuture 对象,这个对象的初始状态是 uncompleted。注意,这个状态下的 IO 是还未开始工作的状态。当 IO 完成之后,不管是 succeeded, failed 或者 cancelled 状态,ChannelFuture 的状态都会转换成为 completed。


下图展示的是 ChannelFuture 状态和 IO 状态的对应图:


                                +---------------------------+                                | Completed successfully    |                                +---------------------------+                           +---->      isDone() = true      |
复制代码


+————————–+ | | isSuccess() = true || Uncompleted | | +=++————————–+ | | Completed with failure || isDone() = false | | +—————————+| isSuccess() = false |—-+—-> isDone() = true || isCancelled() = false | | | cause() = non-null || cause() = null | | +=++————————–+ | | Completed by cancellation || +—————————++—-> isDone() = true || isCancelled() = true |+—————————+


如果要监控 IO 的状态,可以使用上面我们提到的 addListener 方法,为 ChannelFuture 添加一个 ChannelFutureListener。


如果要等待 IO 执行完毕,还有一个 await()方法,但是这个方法会去等待 IO 执行完毕,是一个同步的方法,所以并不推荐。


相比而言,addListener(GenericFutureListener)是一个非阻塞的异步方法,将会把一个 ChannelFutureListener 添加到 ChannelFuture 中,当 IO 结束之后会自动通知 ChannelFutureListener,非常好用。


对于处理 IO 操作的 ChannelHandler 来说,为了避免 IO 的阻塞,一定不要在 ChannelHandler 的 IO 方法中调用 await(),这样有可能会导致 ChannelHandler 因为 IO 阻塞导致性能下降。


下面举两个例子,一个是错误的操作,一个是正确的操作:


// 错误操作 @Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ChannelFuture future = ctx.channel().close();future.awaitUninterruptibly();// 调用其他逻辑}


// 正确操作 @Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ChannelFuture future = ctx.channel().close();future.addListener(new ChannelFutureListener() {public void operationComplete(ChannelFuture future) {// 调用其他逻辑}});}


大家可以对比下上面两种写法的区别。


另外要注意的是 ChannelFuture 中的这些 await 方法比如:await(long), await(long, TimeUnit), awaitUninterruptibly(long), 或者 awaitUninterruptibly(long, TimeUnit)可以带一个过期时间,大家要注意的是这个过期时间是等待 IO 执行的时间,并不是 IO 的 timeout 时间,也就是说当 await 超时之后,IO 还有可能没有执行完成,这就导致了下面的代码有可能报错:


Bootstrap b = ...;ChannelFuture f = b.connect(...);f.awaitUninterruptibly(10, TimeUnit.SECONDS);if (f.isCancelled()) {// 用户取消了 Channel} else if (!f.isSuccess()) {// 这里可能会报异常,因为底层的 IO 可能还没有执行完成 f.cause().printStackTrace();} else {// 成功建立连接}


上面的代码可以改成下面的例子:


Bootstrap b = ...;// 配置连接 timeout 的时间 b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);ChannelFuture f = b.connect(...);f.awaitUninterruptibly();


// 等待直到底层 IO 执行完毕 assert f.isDone();


if (f.isCancelled()) {// 用户手动取消 Channel} else if (!f.isSuccess()) {f.cause().printStackTrace();} else {// 成功建立连接}


Channel 的层级结构 netty 中的 Channel 是有层级结构的,通过 parent 属性可获取这种层级结构。parent 获取的对象和 Channel 的创建方式有关。比如如果是一个被 ServerSocketChannel accepted 的 SocketChannel,那么它的 parent 就是 ServerSocketChannel。


释放资源和所有的 IO 一样,Channel 在用完之后也需要被释放,需要调用 close()或者 close(ChannelPromise) 方法。


事件处理 channel 负责建立连接,建立好的连接就可以用来处理事件 ChannelEvent 了,实际上 ChannelEvent 是由定义的一个个 Channelhandler 来处理的。而 ChannelPipeline 就是连接 channel 和 channelhandler 的桥梁。


我们将会下下一章详细讲解 ChannelEvent、Channelhandler 和 ChannelPipeline 的关联关系,敬请期待。


总结 Channel 在 netty 中是做为一个关键的通道而存在的,后面的 Event 和 Handler 是以 channel 为基础运行的,所以说 Channel 就是 netty 的基础,好了,今天的介绍到这里就结束了,敬请期待后续的文章。


本文已收录于 http://www.flydean.com/04-netty-channel/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 31 分钟前阅读数: 4
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:netty中的Channel详解