想用ZeroMq的发布订阅者模式,又不想写一大串switch case?

想用RPC函数代理机制,又想多对多进行通讯?

下面就结合二者的优点重新封装一套通讯模块

一、先写ZeroMq的发布订阅这模式

  •  先做个代理,负责分发事件,代码如下:
1  // 1. 初始化代理(Proxy)
2 var xSubSocket = new XSubscriberSocket("@tcp://127.0.0.1:61225");
3 var xPubSocket = new XPublisherSocket("@tcp://127.0.0.1:52216");
4 {
5 _proxy = new Proxy(xSubSocket, xPubSocket);
6 // 2. 启动代理(异步运行)
7 var proxyTask = Task.Run(() => _proxy.Start());
8 }
  • 封装客户端代码
  1 using Communication.Nmq.Dto;
2 using System;
3 using System.Collections.Generic;
4
5 namespace Communication.Nmq
6 {
7 public class MqClientMain
8 {
9 public readonly static MqClientMain Instance = new MqClientMain();
10 internal Transceiver _client;
11 private List<MqType> _subscriberList = new();
12 private List<MqType> _unSubscriberList = new();
13 private readonly MethodManager _methodManager = new MethodManager();
14 private bool _isListnerAll;
15 private MqType _owner;
16 protected MqClientMain() { }
17
18
19 /// <summary>
20 /// 函数反射监听(可监听多个)
21 /// </summary>
22 /// <param name="targetInstance"></param>
23 /// <param name="targgetMonitorList"></param>
24 /// <returns></returns>
25 public MqClientMain ProxyAddInstanceMethods<InterfaceType>(InterfaceType targetInstance, params MqType[] targgetMonitorList) where InterfaceType : class
26 {
27 foreach (MqType targgetMonitor in targgetMonitorList)
28 {
29 if (!_subscriberList.Contains(targgetMonitor))
30 _subscriberList.Add(targgetMonitor);
31 }
32
33 _methodManager.AddInstanceMethods(targetInstance);
34 return this;
35 }
36
37
38 /// <summary>
39 ///额外增加事件(可监听多个)
40 /// </summary>
41 /// <param name="targetInstance"></param>
42 /// <param name="mathName"></param>
43 /// <param name="targgetMonitor"></param>
44 /// <returns></returns>
45 public MqClientMain ProxyAddMethods(object targetInstance, string[] mathName, params MqType[] targgetMonitorList)
46 {
47 foreach (MqType targgetMonitor in targgetMonitorList)
48 {
49 if (!_subscriberList.Contains(targgetMonitor))
50 _subscriberList.Add(targgetMonitor);
51 }
52 _methodManager.AddMethods(mathName, targetInstance);
53 return this;
54 }
55
56 /// <summary>
57 /// 开始通讯
58 /// </summary>
59 /// <param name="owner">注册者类型(你是谁)</param>
60 public virtual void Start(MqType owner)
61 {
62 if (_client == null)
63 {
64 if (_isListnerAll)
65 {
66 //监听所有会监听到自己,所以不监听自己
67 _subscriberList.Remove(owner);
68 }
69 _owner = owner;
70 _client = new Transceiver(owner, _subscriberList, _unSubscriberList, _methodManager);
71
72
73 }
74 }
75
76 public void Stop()
77 {
78 _client.Dispose();
79 }
80
81 /// <summary>
82 /// 发布事件
83 /// </summary>
84 /// <param name="msg"></param>
85 public void MqSendMessage(string msg)
86 {
87 if (_client != null)
88 {
89 _client.SendMessage(msg);
90 }
91 }
92
93 /// <summary>
94 /// 代理列表
95 /// </summary>
96 private Dictionary<Type, object> _proxyList = new();
97 /// <summary>
98 /// 获取代理
99 /// </summary>
100 /// <typeparam name="T"></typeparam>
101 /// <returns></returns>
102 public T GetInterfaceProxy<T>() where T : class
103 {
104 if (_client == null)
105 return null;
106 if (!_proxyList.ContainsKey(typeof(T)))
107 _proxyList.Add(typeof(T), InterfaceProxy<T>.Create(_client));
108 return (T)_proxyList[typeof(T)];
109 }
110 }
111 }

二、封装一下RPC的函数代理

  • 封装一个接口代理类
 1  internal class InterfaceProxy<TInterface> : DispatchProxy where TInterface : class
2 {
3 private static Transceiver _client;
4
5 private static JsonSerializerOptions _options = new JsonSerializerOptions
6 {
7 WriteIndented = true, // 让 JSON 格式更加可读
8 Converters = { new JsonStringEnumConverter() } // 使用字符串枚举转换器
9 };
10 internal static TInterface Create(Transceiver client)
11 {
12 object proxy = Create<TInterface, InterfaceProxy<TInterface>>();
13 _client = client;
14 return (TInterface)proxy;
15 }
16 protected override object Invoke(MethodInfo targetMethod, object[] args)
17 {
18 var message = new ProxyMessage
19 {
20 InterfaceType = typeof(TInterface).FullName,
21 Method = targetMethod.Name,
22 Parameters = args,
23 };
24 _client.SendMessage(System.Text.Json.JsonSerializer.Serialize(message, _options));
25 return targetMethod.ReturnType;
26 }
27 }
  • 复制一份RPC封装的获取类里面的所有方法
  1  public class MethodManager
2 {
3 private readonly string[] instanceMethodsOnObject = new string[4] { "Equals", "GetHashCode", "GetType", "ToString" };
4
5 /// <summary>
6 /// 获取一个线程安全的字典,其中键为字符串(不区分大小写),值为另一个线程安全的字典。
7 /// 内部字典的键为整数,值为 Method 对象。
8 /// </summary>
9 public ConcurrentDictionary<string, ConcurrentDictionary<int, Method>> Methods { get; } = new ConcurrentDictionary<string, ConcurrentDictionary<int, Method>>(StringComparer.OrdinalIgnoreCase);
10
11 /// <summary>
12 /// 根据方法名和参数数量获取方法
13 /// </summary>
14 /// <param name="name">方法名</param>
15 /// <param name="paramCount">参数数量</param>
16 /// <returns>找到的方法对象,若未找到则返回null</returns>
17 public Method Get(string name, int paramCount)
18 {
19 if (Methods.TryGetValue(name, out var value) && value.TryGetValue(paramCount, out var value2))
20 {
21 return value2;
22 }
23
24 if (name != "*")
25 {
26 return Get("*", 2);
27 }
28
29 return null;
30 }
31
32 /// <summary>
33 /// 向方法集合中添加一个方法。
34 /// 如果指定方法名称不存在于集合中,则创建一个新的ConcurrentDictionary来存储该方法。
35 /// 根据方法的参数信息,特别是参数类型是否为Context以及是否为可选参数,默认值等信息,
36 /// 将方法添加到对应的ConcurrentDictionary中,键为参数的索引(不包括Context类型的参数)。
37 /// </summary>
38 public void Add(Method method)
39 {
40 if (!Methods.ContainsKey(method.Name))
41 {
42 Methods.TryAdd(method.Name, new ConcurrentDictionary<int, Method>());
43 }
44
45 ConcurrentDictionary<int, Method> concurrentDictionary = Methods[method.Name];
46 ParameterInfo[] parameters = method.Parameters;
47 int num = parameters.Length;
48 int num2 = 0;
49 for (int i = 0; i < num; i++)
50 {
51 ParameterInfo parameterInfo = parameters[i];
52 if (typeof(Context).IsAssignableFrom(parameterInfo.ParameterType))
53 {
54 num2 = 1;
55 }
56 else if (parameterInfo.IsOptional && parameterInfo.HasDefaultValue)
57 {
58 concurrentDictionary.AddOrUpdate(i - num2, method, (int key, Method value) => method);
59 }
60 }
61
62 concurrentDictionary.AddOrUpdate(num - num2, method, (int key, Method value) => method);
63 }
64
65 /// <summary>
66 /// 添加一个方法到集合中,使用指定的方法信息、名称和目标对象。
67 /// </summary>
68 /// <param name="methodInfo">方法的信息。</param>
69 /// <param name="name">方法的名称。</param>
70 /// <param name="target">方法的目标对象,默认为null。</param>
71 public void Add(MethodInfo methodInfo, string name, object target = null)
72 {
73 Add(new Method(methodInfo, name, target));
74 }
75
76 /// <summary>
77 /// 添加一个方法到集合中,使用给定的名称、目标对象和别名。
78 /// </summary>
79 /// <param name="name">方法的名称。</param>
80 /// <param name="target">包含方法的对象。</param>
81 /// <param name="alias">方法的别名,如果为空则使用方法名称。</param>
82 public void AddMethod(string name, object target, string alias = "")
83 {
84 MethodInfo[] methods = target.GetType().GetTypeInfo().GetMethods(BindingFlags.Instance | BindingFlags.Public);
85 if (string.IsNullOrEmpty(alias))
86 {
87 alias = name;
88 }
89
90 MethodInfo[] array = methods;
91 foreach (MethodInfo methodInfo in array)
92 {
93 if (methodInfo.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
94 {
95 Add(methodInfo, alias, target);
96 }
97 }
98 }
99
100 /// <summary>
101 /// 向目标对象添加方法。
102 /// </summary>
103 /// <param name="names">方法名称数组。</param>
104 /// <param name="target">目标对象,方法将添加到该对象上。</param>
105 /// <param name="ns">可选的命名空间前缀,用于区分不同来源的方法。</param>
106 public void AddMethods(string[] names, object target, string ns = "")
107 {
108 foreach (string text in names)
109 {
110 if (string.IsNullOrEmpty(ns))
111 {
112 AddMethod(text, target, text);
113 }
114 else
115 {
116 AddMethod(text, target, ns + "_" + text);
117 }
118 }
119 }
120
121 /// <summary>
122 /// 向目标对象添加实例方法。
123 /// </summary>
124 /// <param name="target">目标对象,其实例方法将被添加。</param>
125 /// <param name="ns">可选的命名空间前缀,用于区分方法名。</param>
126 public void AddInstanceMethods(object target, string ns = "")
127 {
128 MethodInfo[] methods = target.GetType().GetTypeInfo().GetMethods(BindingFlags.Instance | BindingFlags.Public);
129 foreach (MethodInfo methodInfo in methods)
130 {
131 if (Array.IndexOf(instanceMethodsOnObject, methodInfo.Name) == -1)
132 {
133 string text = methodInfo.Name;
134 if (!string.IsNullOrEmpty(ns))
135 {
136 text = ns + "_" + text;
137 }
138
139 Add(methodInfo, text, target);
140 }
141 }
142 }
143 }
  • 通过反射执行方法
  1 using Communication.Nmq.Dto;
2 using NetMQ;
3 using NetMQ.Sockets;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using Communication.Utils;
8 using Newtonsoft.Json;
9
10 namespace Communication.Nmq
11 {
12 internal class Transceiver : IDisposable
13 {
14 private List<MqType> SubscribeTypes;
15 private List<MqType> UnSubscribleTypes;
16 private MethodManager FunListeners;
17 private string Owner;
18 private PublisherSocket ClientPub;
19 private SubscriberSocket ClientSub;
20 private NetMQPoller Poller;
21 private static readonly object SendLock = new();
22
23
24 internal Transceiver(MqType owner, List<MqType> subscribeType, List<MqType> unSubscribleType, MethodManager funListener)
25 {
26 SubscribeTypes = subscribeType;
27 UnSubscribleTypes = unSubscribleType;
28 FunListeners = funListener;
29 Owner = owner.ToString();
30 ClientPub = new PublisherSocket(">tcp://127.0.0.1:61225");
31 ClientSub = new SubscriberSocket(">tcp://127.0.0.1:52216");
32 Poller = new NetMQPoller { ClientSub };
33 SubTopic();
34 ClientSub.ReceiveReady += ClientSub_ReceiveReady;
35 Poller.RunAsync();
36 }
37
38 private void ClientSub_ReceiveReady(object sender, NetMQSocketEventArgs e)
39 {
40 try
41 {
42 List<string> frames = new();
43 if (!e.Socket.TryReceiveMultipartStrings(TimeSpan.FromSeconds(3), ref frames) || frames == null)
44 {
45 Log.Error($"NetMQ接收异常!frames {frames}", LoggerNames.MqStr);
46 return;
47 }
48 if (frames.Count == 2)
49 {
50 string topic = frames[0];
51 string msg = frames[1];
52 if (Enum.TryParse(topic, out MqType topicType))
53 {
54 if (TryDeserializeProxyMessage(msg, out var controlRequest) && !string.IsNullOrWhiteSpace(controlRequest.Method))
55 {
56 if (FunListeners.Methods.TryGetValue(controlRequest.Method, out var methods))
57 {
58 foreach (var methodInfo in methods.Select(m => m.Value))
59 {
60 try
61 {
62 var parameters = controlRequest.Parameters.Select((p, i) => SafeChangeType(p, methodInfo.Parameters[i].ParameterType)).ToArray();
63 methodInfo.MethodInfo?.Invoke(methodInfo.Target, parameters);
64 }
65 catch (Exception ex)
66 {
67 Log.Error($"Failed to convert parameter for method {controlRequest.Method}: {ex.Message}", LoggerNames.MqStr);
68 return;
69 }
70 }
71 }
72 else
73 {
74 throw new InvalidOperationException("找不到对应的函数");
75 }
76 }
77 else
78 {
79 throw new InvalidOperationException("无法转换格式");
80 }
81 }
82 else
83 {
84 Log.Error($"NetMQ收到不正常数据,请检测!MqType:{topic}", LoggerNames.MqStr);
85 }
86 }
87 else
88 {
89 Log.Error($"NetMQ收到不正常数据,请检测!frames 长度为{frames.Count}", LoggerNames.MqStr);
90 }
91 }
92 catch (Exception ex)
93 {
94 Log.Error($"NetMQ收到消息报错:{ex.ToString()}", LoggerNames.MqStr);
95 }
96 }
97
98 public object SafeChangeType(object value, Type targetType)
99 {
100 if (targetType.IsEnum && value is string strValue)
101 {
102 return Enum.Parse(targetType, strValue);
103 }
104 return Convert.ChangeType(value, targetType);
105 }
106
107 private bool TryDeserializeProxyMessage(string json, out ProxyMessage message)
108 {
109 message = null;
110 try
111 {
112 message = JsonConvert.DeserializeObject<ProxyMessage>(json);
113 return message != null;
114 }
115 catch
116 {
117 return false;
118 }
119 }
120
121 private void SubTopic()
122 {
123 if (SubscribeTypes?.Any() == true)
124 {
125 foreach (var item in SubscribeTypes)
126 {
127 ClientSub.Subscribe(item.ToString());
128 }
129 }
130 if (UnSubscribleTypes?.Any() == true)
131 {
132 foreach (var item in UnSubscribleTypes)
133 {
134 ClientSub.Unsubscribe(item.ToString());
135 }
136 }
137 }
138
139 internal void SendMessage(string msg)
140 {
141 try
142 {
143 lock (SendLock)
144 {
145 var success = ClientPub.SendMoreFrame(Owner).TrySendFrame(TimeSpan.FromSeconds(3), msg);
146 }
147 }
148 catch (Exception ex)
149 {
150 Log.Error($"发送_消息 失败 {msg},:{ex}", LoggerNames.MqStr);
151 }
152 }
153
154 public void Dispose()
155 {
156 Poller.Stop();
157 ClientPub?.Dispose();
158 ClientSub?.Dispose();
159 Poller.Dispose();
160 }
161 }
162 }

c#运用ZeroMq发布订阅和RPC函数代理的优点结合成一个新的实用的通讯的更多相关文章

  1. python redis 发布订阅 实现 RPC同步

    工作中用到的场景是,python主程序发布消息到Redis,然后停住等待Redis上订阅的Response.等待过程是阻塞的,相当于把异步通信封装成同步通信,类似于Java的RPC. RPC封装的代码 ...

  2. Javascript设计模式之发布-订阅模式

    简介 发布-订阅模式又叫做观察者模式,他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知. 回忆曾经 作为一名前端开发人员,给DOM节点绑定事件可是再频繁不过 ...

  3. RPC服务的发布订阅实现Thrift

    Thrift 个人实战--RPC服务的发布订阅实现(基于Zookeeper服务) 前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的 ...

  4. Go 语言实现 gRPC 的发布订阅模式,REST 接口和超时控制

    原文链接: 测试小姐姐问我 gRPC 怎么用,我直接把这篇文章甩给了她 上篇文章 gRPC,爆赞 直接爆了,内容主要包括:简单的 gRPC 服务,流处理模式,验证器,Token 认证和证书认证. 在多 ...

  5. AlterNats是如何做到高性能的发布订阅的?

    前言 在过去的一些文章里面,我们聊了一些.NET平台上高性能编程的技巧,今天带大家了解一下AlterNats这个库是如何做到远超同类SDK性能的. NATS:NATS是一个开源.轻量级.高性能的分布式 ...

  6. 分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载

    一.分布式消息总线以及基于Socket的实现 在前面的分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载一文之中给大家分享和介绍了一个极其简单也非常容易上的基于.N ...

  7. 分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载

    一.分布式消息总线 在很多MIS项目之中都有这样的需求,需要一个及时.高效的的通知机制,即比如当使用者A完成了任务X,就需要立即告知使用者B任务X已经完成,在通常的情况下,开发人中都是在使用者B所使用 ...

  8. NetMQ(三): 发布订阅模式 Publisher-Subscriber

    ZeroMQ系列 之NetMQ 一:zeromq简介 二:NetMQ 请求响应模式 Request-Reply 三:NetMQ 发布订阅模式 Publisher-Subscriber 四:NetMQ ...

  9. MQTT 消息 发布 订阅

    当连接向一个mqtt服务器时,clientId必须是唯一的.设置一样,导致client.setCallback总是走到 connectionLost回调.报connection reset.调查一天才 ...

  10. C# 委托和事件 与 观察者模式(发布-订阅模式)讲解 by天命

    使用面向对象的思想 用c#控制台代码模拟猫抓老鼠 我们先来分析一下猫抓老鼠的过程 1.猫叫了 2.所有老鼠听到叫声,知道是哪只猫来了 3.老鼠们逃跑,边逃边喊:"xx猫来了,快跑啊!我是老鼠 ...

随机推荐

  1. 分享5款开源、美观的 WinForm UI 控件库

    前言 今天大姚给大家分享5款开源.美观的 WinForm UI 控件库,助力让我们的 WinForm 应用更好看. WinForm WinForm是一个传统的桌面应用程序框架,它基于 Windows ...

  2. Spring AI 1.0 正式发布!核心内容和智能体详解

    在经历了八个里程碑式的版本之后(M1~M8),Spring AI 1.0 正式版本,终于在 2025 年 5 月 20 日正式发布了,这是另一个新高度的里程碑式的版本,标志着 Spring 生态系统正 ...

  3. Maven中配置maven-compiler-plugin 插件和jdk 17版本

    如何修改Maven工程的JDK版本 修改项目中的pom.xml文件,添加maven-compiler-plugin插件3.8.1版本,指定JDK的编译版本为Java 17,简约版配置信息如下: < ...

  4. 【洛谷有题】NOI 笔试题库(非初赛)订正

    传送门 第一次做,那个成绩可是一个惨不忍睹-- 我还是想说--我虽然要用Linux,但是不一定要用到指令啊(吧)--编译啥的我可以用Vim|guide啊-- Linux 中为文件改名使用的命令是: m ...

  5. Burp Suite 企业级深度实战教程

    第一部分:环境搭建与高级配置 1.1 专业版激活与插件生态 # 专业版激活(Linux) java -jar -Xmx2048m burpsuite_pro.jar --activate --acti ...

  6. [Minecraft] Eon 扩展包

    Release Eon 扩展包 摘要&前言 是的,原版装备不够 OP,于是我做了这个模组.额外地,我添加了一些有趣的物品. 非原创内容(仅有贴图) 声明:本模组中使用的非原创内容均不用于盈利. ...

  7. Caddy自编译

    转载自我的个人博客:Caddy自编译 配置 Golang 环境 需要先配置 Golang 环境:Download and install - The Go Programming Language w ...

  8. games101作业环境一站式配置(2022vs版)

    在尝试配置Eigen和opencv多次失败后,找到一位大佬做的一键配置的GAMES101的作业框架以及运行环境,按照步骤安装后,真的是帮助非常大,特别感谢大佬! 这是大佬的链接: https://gi ...

  9. C++ map案例学习总结

    公司招聘5个员工,五名员工进入公司之后,需要指派员工在哪个部门工作 人员信息:姓名 年龄 电话 工资 等组成 通过Multimap进行信息的插入 保存 显示 分部门显示员工信息 显示全部员工信息 #i ...

  10. ICLR2025-MMFNET:用于多变量时间序列预测的多尺度频率掩码神经网络

    title:MMFNET: MULTI-SCALE FREQUENCY MASKING NEURAL NETWORK FOR MULTIVARIATE TIME SERIES FORECASTING ...