Netty 是一个可以快速开发网络应用程序的基于事件驱动的异步 网络通讯 框架,它大大简化了 TCP 或者 UDP 服务器的网络编程。Netty 的应用还是比较广泛的,比如阿里巴巴开源的 Dubbo 和 Sofa-Bolt等 框架底层网络通讯都是基于 Netty 来实现的。Netty的设计是精妙的,其中每个设计点都值得我们去深思,本节我们来看看Netty中@Sharable的设计哲学
如果你熟悉netty请直接看第三节
io.netty.channel.Channel 是 Netty 框架自己定义的一个通道接口,Netty 实现的客户端 NIO 套接字通道是 NioSocketChannel,提供的服务器端 NIO 套接字通道是 NioServerSocketChannel。
在 Netty 中客户端持有一个 EventLoopGroup 用来处理网络 IO 操作,在服务器端持有两个 EventLoopGroup,其中 boss 组是专门用来接收客户端发来的 TCP 链接请求的,worker 组是专门用来具体处理完成三次握手的链接套接字的网络 IO 请求的。
当 Channel 是客户端通道 NioSocketChannel 时候,会注册 NioSocketChannel 管理的 SocketChannel 实例到自己关联的 NioEventLoop 的 selector 选择器上,然后 NioEventLoop 对应的线程会通过 select 命令监控感兴趣的网络读写事件;
当 Channel 是服务端通道 NioServerSocketChannel 时候,NioServerSocketChannel 本身会被注册到 boss EventLoopGroup 里面的某一个 NioEventLoop 管理的 selector 选择器上,而完成三次握手的链接套接字是被注册到了 worker EventLoopGroup 里面的某一个 NioEventLoop 管理的 selector 选择器上;
需要注意是多个 Channel 可以注册到同一个 NioEventLoop 管理的 selector 选择器上,这时候 NioEventLoop 对应的单个线程就可以处理多个 Channel 的就绪事件;但是每个 Channel 只能注册到一个固定的 NioEventLoop 管理的 selector 选择器上。
需要注意一点是虽然每个 Channel(更底层说是每个 socket)有自己的 ChannelPipeline,但是每个 ChannelPipeline 里面可以复用通一个 ChannelHandler(也就是标注了@Sharable注解的handler可以被复用)。
上节我们提到每个 Channel(更底层说是每个 socket)有自己的 ChannelPipeline,每个 ChannelPipeline里面管理者一系列的ChannelHandler。
正常情况下每个 Channel自己的 ChannelPipeline管理的同一个ChannelHandler Class对象的实例都是直接new的一个新实例,也就是原型模式,而不是单例模式。
如上图当我们启动Netty服务端时候,会设置childHander,这个childHander会当服务器接受到完成TCP三次握手链接的时候给当前完成握手的Channel通道创建一个ChannelPipeline,并且创建一个EchoServerHandler的实例加入到Channel通道的ChannelPipeline。也就是说服务器接受的所有Channel对应的ChannelPipeline里面管理者自己的EchoServerHandler实例,而不是同一个。
但是有时候我们却想让不同Channel对应的ChannelPipeline里面管理同一个EchoServerHandler实例,比如为了全局的一些统计信息,既然上面说当服务器接受到完成TCP三次握手链接的时候给当前完成握手的Channel通道创建一个ChannelPipeline,并且创建一个EchoServerHandler的实例加入到Channel通道的ChannelPipeline,那么我们创建一个单例的EchoServerHandler传递给childHandler是不是就可以了?我们修改上面代码如下:
这样当服务器接受到完成TCP三次握手链接的时候给当前完成握手的Channel通道创建一个ChannelPipeline,并且添加同一个EchoServerHandler的实例到对应管道。
启动上面代码,然后客户端发起多个链接时候,会有下面结果:
这是因为我们的EchoServerHandler没有被添加@Sharable注解,现在添加如下:
再次运行就OK了。
具体检查的代码是DefaultChannelPipeline类的checkMultiplicity的checkMultiplicity方法:
可知当添加到不同管线的是不同的实例时候,不同连接在检查时候h.added总是返回的false,所以不会抛出异常。当添加到不同管线的是同一个实例时候,由于是单例,所以第一个连接会把单例的对象的added设置为了true,所以其他连接检查时候发现没有添加@Sharable注解并且当前added为true则会抛出异常。
正常情况下同一个ChannelHandler,的不同的实例会被添加到不同的Channel管理的管线里面的,但是如果你需要全局统计一些信息,比如所有连接报错次数(exceptionCaught)等,这时候你可能需要使用单例的ChannelHandler,需要注意的是这时候ChannelHandler上需要添加@Sharable注解。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。