写在前面
这篇文章写了改,改了写,中间耽搁好长时间,最终还是决定坚持写下来,因为我自己在学习这部分开发时也花了很长时间去理解,所以这篇文章也相当于是对我这部分开发和学习的一个总结,希望它能给你带来帮助。
因为本人能力有限,所以文中有些写的不明白或者有错误的地方还请大佬批评指正,我也会不断在项目中进行总结,更新这篇文章,让其更加通俗易懂!
背景介绍
在MES项目开发中,我们不希望经常改动主程序,但因为不同客户要求不同,我们发送给客户的数据格式,数据内容都可能有所不同,所以我们的主程序要做到通用,然后根据不同客户要求通过开发不同的监控程序来监控主程序获取数据并根据客户要求进行处理,达到客户要求。
譬如说A、B两家客户都想要主程序采集到的温度数据,但是A想用Log文件方式记录温度数据,B想用波形图形式实时监控温度,那么我们主程序就需要有这样一个实时获取温度的接口,监控程序A获取温度后记录至Log中,监控程序B获取温度后要展示在界面上,这个记录Log和展示的功能如果放在主程序中,时间久了,主程序就会变得越来越庞大,越来越难以维护,但是如果根据不同客户要求开发不同监控程序处理数据,主程序只负责提供接口和监控数据,那么就真正做到低耦合,如果哪天客户A说我不想记录到Log中,你给我实时发送到我的服务器中,那我只需要将监控程序A中的这部分功能改掉,主程序根本不需要动。
关键技术
让监控程序与主程序通讯有很多方法, 本文介绍以Json RPC2.0为协议,Socket为通讯方式的一种通讯方法,能够让监控程序接收主程序数据并且可以调用主程序方法以达到控制主程序的功能。本项中会使用Austin Harris的Json RPC2.0库,它能够让数据转化成json格式,然后通过Socket发送,关于json-rpc 2.0的规范,大家可以参考http://wiki.geekdream.com/Specification/json-rpc_2.0.html。
项目介绍
下面我们就以温度监控为例,开发一个包含Server和Client的项目。如下图所示,界面1是MainServer,需要获取温度数据,假设这个温度来自于按钮Start Heating,按钮每按一次温度加热1°C,加热时需要把温度传给界面2 SubClient,这时就需要有一个接口函数GetTemp(string temp)来发送数据给SubClient,当温度达到20°C时,需要停止机器加热,那么2就要在温度达到20时控制1停止加热,所以我这里的设计当2检测到Current Temp from Server文本框内的值达到20时,禁用1中的Start Heating按钮,这就需要另外一个接口函数 StopHeating()。
需要注意的是,当SubClient主动向MainServer发送数据时,SubClient相当于是Server,MainServer是Client,需要监听端口判断是否有数据发送过来。
具体步骤
项目结构
建立如下解决方案。
MainServer应用程序:
Server界面,如上图1;
PlugInInterface类库:
AsyncTcpServer主要定义了Socket通信和数据处理,当客户端接收到来自远程的Json数据时,需要通过Json Rpc2.0库启动客户端中注册的函数;
JsonRpcClient主要定义了在Server发送数据时,需要将数据转化成Json格式,然后通过指定端口将数据发送出去;
PlugInManager类库:
Server文件夹中定义了接口函数,其中,ServerTemp中定义了MainServer需要发送至SubClient中的方法,ClientTemp定义了SubClient需要主动向MainServer发送数据的方法;
PluginManager中是AsyncTcpServer和RPCServer的实例化,并启动Socket监听;
RPCServer中定义了MainServer通过Socket发送数据至SubClient的实现方法,并开启端口监听,用来监听是否有来自SubClient的数据发送。
SubClient应用程序:
Client界面,如上图2。
这里可能介绍地比较简单,大家可能看不懂,没关系,我会在下面列一个详细的流程图,然后结合代码,应该就能看懂了。
namespace PlugInManager.Server
{
public class ServerTemp
{
RPCServer Server;
public ServerTemp(RPCServer server)
{
Server = server;
}
public void GetTemp(string temp)
{
Server.AddToCmdList("GetTemp", temp);
}
}
public class ClientTemp : JsonRpcService
{
public bool IsStopHeating = false;
[JsonRpcMethod]
public bool StopHeating()
{
var functionname = System.Reflection.MethodBase.GetCurrentMethod().Name;
IsStopHeating = true;
return true;
}
}
}
项目流程
如下图所示是整个项目的流程图,左边黑色字体标注的是Server的软件流程,右边红色字体标注的是Client的软件流程。
Server
1 MainServer软件启动;
2 创建StopHeating()函数,需要继承JsonRpcService,当ClientStopHeating动作执行时,该函数才会触发,这里的逻辑是当温度达到20°C时,SubClient控制MainServer禁用按钮控件;
3 注册Server中的函数,需要将GetTemp()函数注册在执行温度增加的按钮中;
4 开始监听3333端口,看是否有来自客户端的消息,4,5,6步分别执行创建对象,分配端口,开始监听;
5 第7步实际上就是Server界面上执行StartHeating动作了,这时就GetTemp函数也会发生,如下列函数所示:
if (IsStartHeating)
{
IsStartHeating = false;
Application.Current.Dispatcher.Invoke(
new Action(() =>
{
HeatTextBox.Text = string.Empty;
HeatTextBox.Text = i.ToString();
PlguinManager.handle.STemp.GetTemp(i.ToString());
}));
}
6 这里就是执行步骤3中的register,代码中GetTemp()中将函数名Add到Server中,这里实际上可以理解为将该函数发布出去,发布到3999端口,这样执行以后Client端才能接收到该函数的执行。
public void AddToCmdList(string commandName, params object[] arr)
{
p.cmdlists.Enqueue(new CmdItem(commandName, arr));
}
7 所有需要发布到Client的函数都需要加载cmdlists中,然后对cmdlists中的每一项进行发布。
private void ExecuteCmd(string commandName, object[] arr)
{
if (Handle == null) return;
JsonResponse<object> response;
var request = Handle.Invoke<object>(commandName, arr.ToArray(), Scheduler.Default);
request.Subscribe(
onNext: _ =>
{
response = _;
},
onError: _ =>
{
isbusy = false;
},
onCompleted: () => { isbusy = false; }
);
}
然后通过Socket发送至客户端。
Client
Client端和Server端的原理相同,当需要接收数据时,它就作为Client,当需要发送数据时它就作为Server,所以这里也不再作累述,可以看Client的代码。
获取实时温度:
public class Temp : JsonRpcService
{
public string Servertemp = "";
public bool IsRead { get; set; }
[JsonRpcMethod]
public void GetTemp(string temp)
{
var functionname = System.Reflection.MethodBase.GetCurrentMethod().Name;
Servertemp = temp;
IsRead = true;
}
控制MainServer停止加热:
public bool StopHeating()
{
AddToCmdList("StopHeating");
return true;
}
总之,只要记住,发送数据时需要注册,需要AddToCmdList,接收数据时需要继承JsonRpcService,这样服务器端有动作时客户端才能接收到。
写在最后
文章中提到的JSONRPC的库,这是一个开源的库https://github.com/Astn/JSON-RPC.NET里面有源代码,有实例,写的也非常详细。