[[IT知识]] @Sharable注解深度解析:实现Netty ChannelHandler多管道共享的关键

[复制链接]
查看: 40|回复: 0
发表于 2025-1-21 12:17:38 | 显示全部楼层 | 阅读模式
易博V9下载

@Sharable注解深度解析:实现Netty ChannelHandler多管道共享的关键

一、@Sharable概述

表示可以将带注释的 ChannelHandler 的同一个实例多次添加到一个或多个 ChannelPipelines 中,而不会出现竞争条件。如果未指定此注解,则每次将其添加到管道时都必须创建一个新的处理程序实例,因为它具有成员变量等非共享状态。(这个是Netty的官方给的说明)

简单的理解:

  • @Sharable是用来修饰ChannelHandler的
  • ChannelHandler单例模式下需要添加多个ChannelPipelines 也就是要拦截多个Channel,就需要使用到@Sharable来修饰ChannelHandler

二、示例验证

在Netty中添加 ChannelHandler 的代码如下(代码来源Netty官网):

  1. public class DiscardServer {
  2. private int port;
  3. public DiscardServer(int port) {
  4. this.port = port;
  5. }
  6. public void run() throws Exception {
  7. EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
  8. EventLoopGroup workerGroup = new NioEventLoopGroup();
  9. try {
  10. ServerBootstrap b = new ServerBootstrap(); // (2)
  11. b.group(bossGroup, workerGroup)
  12. .channel(NioServerSocketChannel.class) // (3)
  13. .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
  14. @Override
  15. public void initChannel(SocketChannel ch) throws Exception {
  16. ch.pipeline().addLast(new DiscardServerHandler()); //(8)
  17. }
  18. })
  19. .option(ChannelOption.SO_BACKLOG, 128) // (5)
  20. .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
  21. ChannelFuture f = b.bind(port).sync(); // (7)
  22. f.channel().closeFuture().sync();
  23. } finally {
  24. workerGroup.shutdownGracefully();
  25. bossGroup.shutdownGracefully();
  26. }
  27. }
  28. public static void main(String[] args) throws Exception {
  29. int port = 8080;
  30. if (args.length > 0) {
  31. port = Integer.parseInt(args[0]);
  32. }
  33. new DiscardServer(port).run();
  34. }
  35. }
复制代码

把上面代码稍微做一点修改,将(8)位置代码修改成如下:

  1. //在DiscardServer定义一个变量
  2. private DiscardServerHandler handler = new DiscardServerHandler();
  3. //(8位置改成如下)
  4. ch.pipeline().addLast(handler);
复制代码

当往多个 ChannelChannelPipeline 中添加同一个 ChannelHandler 的时候,就会判断该实例是否增加了 @Sharable 注解。如果没有就会抛出错误:

  1. io.netty.channel.ChannelPipelineException: com.github.mxsm.netty.TimeServerHandler is not a @Sharable handler, so can't be added or removed multiple times.
复制代码

Tips: 上面的错误是代码演示抛出来的,下面会根据代码分析

原因分析:

  • 以官网的例子进行运行,添加的不是单例,加不加@Sharable注解并没有什么关系。
  • 如果你添加的是单例,并且会被添加到多个Channel的 ChannelPipelines中,就必须加上@Sharable。否则就会报错

Tips: 在 initChannel 方法中ChannelHandler是否单例和Netty没关系,也和@Sharable修饰ChannelHandler是否单例化没有关系。这个是否单例与使用者有关。如果是一上面的 new 的形式。那么 DiscardServerHandler 就不是单例。与有没有加@Sharable没关系。

三、使用示例

  1. import com.lilinchao.netty.config.Config;
  2. import com.lilinchao.netty.config.Serializer;
  3. import com.lilinchao.netty.message.Message;
  4. import io.netty.buffer.ByteBuf;
  5. import io.netty.channel.ChannelHandler;
  6. import io.netty.channel.ChannelHandlerContext;
  7. import io.netty.handler.codec.MessageToMessageCodec;
  8. import lombok.extern.slf4j.Slf4j;
  9. import java.util.List;
  10. /**
  11. * @author lilinchao
  12. * @date 2022/6/17
  13. * @description 消息注解
  14. * 必须和 LengthFieldBasedFrameDecoder 一起使用,确保接到的 ByteBuf 消息是完整的
  15. **/
  16. @Slf4j
  17. @ChannelHandler.Sharable
  18. public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {
  19. @Override
  20. protected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
  21. ByteBuf out = ctx.alloc().buffer();
  22. // 1. 4 字节的魔数
  23. out.writeBytes(new byte[]{1, 2, 3, 4});
  24. // 2. 1 字节的版本,
  25. out.writeByte(1);
  26. // 3. 1 字节的序列化方式 jdk 0 , json 1
  27. out.writeByte(Config.getSerializerAlgorithm().ordinal());
  28. // 4. 1 字节的指令类型
  29. out.writeByte(msg.getMessageType());
  30. // 5. 4 个字节
  31. out.writeInt(msg.getSequenceId());
  32. // 无意义,对齐填充
  33. out.writeByte(0xff);
  34. // 6. 获取内容的字节数组
  35. byte[] bytes = Config.getSerializerAlgorithm().serialize(msg);
  36. // 7. 长度
  37. out.writeInt(bytes.length);
  38. // 8. 写入内容
  39. out.writeBytes(bytes);
  40. outList.add(out);
  41. }
  42. @Override
  43. protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception {
  44. int magicNum = in.readInt();
  45. byte version = in.readByte();
  46. byte serializerAlgorithm = in.readByte(); // 0 或 1
  47. byte messageType = in.readByte(); // 0,1,2...
  48. int sequenceId = in.readInt();
  49. in.readByte();
  50. int length = in.readInt();
  51. byte[] bytes = new byte[length];
  52. in.readBytes(bytes, 0, length);
  53. // 找到反序列化算法
  54. Serializer.Algorithm algorithm = Serializer.Algorithm.values()[serializerAlgorithm];
  55. // 确定具体消息类型
  56. Class<? extends Message> messageClass = Message.getMessageClass(messageType);
  57. Message message = algorithm.deserialize(messageClass, bytes);
  58. // log.debug("{}, {}, {}, {}, {}, {}", magicNum, version, serializerType, messageType, sequenceId, length);
  59. // log.debug("{}", message);
  60. out.add(message);
  61. }
  62. }
复制代码

MessageToMessageCodec类将已经被处理的完整数据再次被处理。传过来的Message如果是被处理过的完整数据,那么被共享也就不会出现问题了,也就可以使用@Sharable注解。

总结

  • 网上很多说这个@Sharable跟ChannelHandler是单例有关,其实没有什么关系。ChannelHandler是否为单例取决于使用者添加的是否为单例。和开发者的行为有关。但是如果你想使用单例的ChannelHandler添加到ChannelPipeline中那么就需要用@Sharable进行修饰。
  • ChannelHandler可以作为一个全局的统计,例如用户连接数量的统计就可以注册一个单例ChannelHandler来实现。
易博软件介绍
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1、请认真发帖,禁止回复纯表情,纯数字等无意义的内容!帖子内容不要太简单!
2、提倡文明上网,净化网络环境!抵制低俗不良违法有害信息。
3、如果你对主帖作者的帖子不屑一顾的话,请勿回帖。谢谢合作!
3、问答求助区发帖求助后,如有其他用户热心帮您解决问题后,请自觉点击设为最佳答案按钮。

 
 
QQ在线客服
QQ技术支持
工作时间:
8:00-18:00
软著登字:
1361266号
官方微信扫一扫
weixin

QQ|小黑屋|Archiver|慈众营销 ( 粤ICP备15049986号 )|网站地图

自动发帖软件 | 自动发帖器 | 营销推广软件 | 网络营销工具 | 网络营销软件 | 网站推广工具 | 网络推广软件 | 网络推广工具 | 网页推广软件 | 信息发布软件 | 网站推广工具 | 网页推广软件

Powered by Discuz! X3.4   © 2012-2020 Comsenz Inc.  慈众科技 - Collect from 深圳吉宝泰佛文化有限公司 公司地址:罗湖区黄贝街道深南东路集浩大厦A1403

返回顶部 返回列表