分布式系统有很多成熟的解决方案. 如: 微软的 WCF.WCF 太过于复杂, 配置也麻烦. 其实可以自己动手设计一个小的分布式系统. 系统的原理完全在自己掌握之中, 可以根据业务随机而变. 这里展示远程调用最核心最基本的处理逻辑, 其实远程调用并不复杂神秘.
分布式系统其实是数据流的交换. 数据必须快速的从一段传送到另一端, 否则系统性能就大打折扣. 对于. net, 本人设计一个非常优化易于使用的网络库 (EasyNetMessage). 使用该库, 不需要关心底层细节, 所有处理对象是 string,byte; 发送时, 不需要处理分包 (几十 M 的数据也可以一次发送); 收包时, 不需要处理粘包. 本文所示例代码, 就是基于该网络库. 本文章实例代码见底部.
实现目标
实现一个非常简单的应用 internal int AddCall(int value1, int value2); 就是两个整数相加. 调用过程和本地完全一样, 只是执行加法操作是在远程服务器实现的. 操作非常简单, 但是揭示了远程调用的核心处理流程.
处理过程:
a) 数据发送
每次函数调用必须有一个唯一标识 CallId, 这个也是发包的唯一标识; 服务端处理完后, 返回的结果也带有此标识. 通过此标识, 将发送端数据和返回数据关联起来了.
发送完数据后, 客户端线程要挂起, 等待服务器端的处理结果. 线程挂起使用 ManualResetEvent. 并建立起 CallId 与 ManualResetEvent 的对应关系. 当数据返回时, 就能找到对应的 ManualResetEvent.
- //callid 与事件关联
- Dictionary<int, ManualResetEvent> _callEventGroup = new Dictionary<int, ManualResetEvent>();
- //callId 与返回结果关联
- Dictionary<int, NetCallAddAck> _callResultGroup = new Dictionary<int, NetCallAddAck>();
- internal int AddCall(int value1, int value2)
- {
- // 组织发送包
- NetCallAdd add = new NetCallAdd();
- add.Value1 = value1.ToString();
- add.Value2 = value2.ToString();
- MonitorClient client = GetCurAppClient();
- if (client == null)
- throw new Exception("socket 未连接!");
- // 生成线程事件, 并与 CallId 关联
- ManualResetEvent callEvent = new ManualResetEvent(false);
- lock (_callEventGroup)
- {
- _callEventGroup.Add(add.CallId, callEvent);
- }
- // 发送数据
- EN_SendDataResult result = _netServer.SendData(client.ClientSocket, add.ToEasyMessage().ToNetPacket());
- if (result != EN_SendDataResult.ok)
- {
- lock (_callEventGroup)
- {
- _callEventGroup.Remove(add.CallId);
- }
- throw new Exception("网络发送异常!");
- }
- // 等待结果
- callEvent.WaitOne(3000);
- _callEventGroup.Remove(add.CallId);
- // 查看结果集
- lock (_callResultGroup)
- {
- if (_callResultGroup.ContainsKey(add.CallId))
- {
- NetCallAddAck ack = _callResultGroup[add.CallId];
- return int.Parse(ack.Result);
- }
- }
- throw new Exception("没有返回结果!");
- }
b) 数据返回
数据返回后的处理是在另一个线程. 数据返回后, 先根据 CallId 查找对应的 ManualResetEvent; 如果找不到, 有可能服务器处理太慢, 超时了.
先将返回结果存储到哈希数组中, key 为 CallId. 再调用 ManualResetEvent 的 Set 函数, 唤醒调用端线程. 调用端线程根据 CallId 到哈希表中获取结果.
- internal void OnRcvAck(NetCallAddAck addAck)
- {
- // 根据 callid 找到事件
- ManualResetEvent callEvent = null;
- lock (_callEventGroup)
- {
- if (!_callEventGroup.ContainsKey(addAck.CallId))
- return;
- callEvent =_callEventGroup[addAck.CallId];
- }
- // 结果存放到哈希表中
- lock (_callResultGroup)
- {
- _callResultGroup.Remove(addAck.CallId);
- _callResultGroup.Add(addAck.CallId, addAck);
- }
- // 设置事件为有信号, 调用方挂起的线程可以继续执行
- callEvent.Set();
- }
进一步说明: 可以在此基础上, 进一步扩展. 开发出类似 Redis 的内存库. 客户端的调用也不一定是同步 , 可以采用异步回调的方式处理. 其实如果知道处理的原理, 可以根据自己的业务做裁剪. 只有知其所以然, 才能开发出最符合自己业务的系统, 才可能进一步优化.
实例代码 https://download.csdn.NET/download/qq_29939347/10684858 (代码已上传, 但是需要审核, 最近可能无法下载)
来源: https://www.cnblogs.com/yuanchenhui/p/remoteCall.html