1 gRPC介绍和缘起
gRPC 是一个高性能、开源的远程过程调用(RPC)框架,由 Google 开发并基于 HTTP/2 协议。它支持多种编程语言,并默认使用 Protocol Buffers(protobuf)作为接口定义语言(IDL)和数据序列化工具。gRPC 的设计目标是简化跨语言、跨平台的分布式系统开发。
gPRC可以支持多种编程语言(如 C++,C#, Java, Python, Go 等),通过 protobuf 实现接口定义的统一。并且有着性能优势:
- 二进制编码(protobuf)比 JSON/XML 更高效。
- HTTP/2 的多路复用减少连接开销。
- 支持异步和非阻塞 I/O,适合高并发场景。
以上种种特点,使得gRPC成为现代分布式系统的理想选择,尤其适合性能敏感和复杂通信模式的场景。
1.1 IOT-Tree对gPRC的支持
IOT-Tree Server目前在内部实现了一个gRPC服务,通过protobuf接口定义和gRPC Server端实现,已经能够支持各种其他语言开发各自的客户端程序。由于IOT-Tree基于项目对设备接入数据做了统一(规整为标签Tag),所以客户端要对应的接口整体也就得到了极大的简化。
1.2 iottree.net开源项目
在官方开源项目 iottree.net 中,使用了C#实现了gRPC客户端调用封装,这更加简化了Api,可以让你很轻松的就可以完成基于IOT-Tree Server专有客户端。
本文就是以 开源项目 iottree.net为基础,讲述如何使用微软Visual Studio C#.NET 开发客户端。
1.3 优缺点
1 IOT-Tree Server在后台已经为你现场设备(如各种PLC)做好了对接,你要实现自己的客户端只需要支持IOT-Tree Server已经整理好的标签列表,每个标签使用类似 xxx.xx.xxx 格式的唯一路径标识。
这样你的客户端软件不需要考虑设备各种差异信息------如PLC中的数据地址等。这可以大大简化你的客户端开发过程。
2 使用gPRC可以使得一个IOT-Tree Server实例支持多个客户端同时访问,这在一些分布式管理现场会有很大的好处。
3 如果你运行的是单机系统,IOT-Tree Server和你的.net客户端程序运行在同一个系统中,这样可以达到最好的性能。
缺点:你的客户端程序必须配合IOT-Tree Server一起发布。
你如果对IOT-Tree Server还不了解,可以参考其他文章更多了解IOT-Tree能给你带来的好处:
使用IOT-Tree Server通过MC协议连接三菱Q系列PLC
使用IOT-Tree Server连接西门子PLC S7-300/1200/1500
使用IOT-Tree Server通过PPI(RS485)连接西门子PLC S7-200
系列文章还包含IOT-Tree完成一个具体项目的过程,非常详细,如下:
2 整体开发过程说明
2.1 IOT-Tree Server运行环境
我们还是以IOT-Tree Server中的Demo项目作为本文案例,你可以参考如下文档,快速搭建这个运行环境和项目:
快速开始
https://gitcode.com/jasonzhu8888/iot-tree/blob/main/web/doc/cn/doc/quick_start.md
2.1.1 启用gRPC服务
上面的IOT-Tree Server运行实例和项目能够正常运行之后,你需要启用gPRC服务。在IOT-Tree Server管理主界面(非项目管理界面),点击服务程序(Services)里面的"设置Setup"按钮,在弹出的服务列表中,有个"gRPC Server"。如下图:

点击对应的"编辑"图标,在弹出的窗口中,使能此服务,并且选择你希望的对外服务端口,完成之后,就可以启动此service了。如下:

2.2 C#.Net开发说明
2.2.1 运行环境准备
微软Visual Studio开发环境,我这里就不说了,因为你能看到这里应该也熟悉相关技术。
访问下载 iottree.net 这个配套开源项目。使用微软Visual Studio软件打开工程。你可以看到里面有3个子项目。其中lib是使用c#对访问IOT-Tree Server的gRPC接口调用进行了封装,这样可以提供更直观简单的调用接口。另外两个是console demo和client demo。
你可以编译启动iottree_client_demo这个项目。界面如下:

你需要设置IOTTree Server gRPC地址和端口,且每个client都必须有自己的一个唯一id,以便于IOT-Tree Server区分不同的客户端。
Tag Path是此客户端需要监听同步和控制输出的标签列表,它的格式是 prj_name.xx.xx.tag_name。很明显,IOT-Tree Server中可以同时运行多个项目,而一个客户端可以同时监听多个项目中的数据标签。通过这种方式,可以发现IOT-Tree Server的客户端调用非常简单,只需要以这种唯一标签列表作为基础,而不需要考虑各种设备的复杂通信协议。因为这些复杂性都由IOT-Tree Server的项目给你搞定了。
点击按钮"Start Client",你可以发现正常连接成功之后,运行效果:

你可以通过下面的按钮控制水泵的启动和停止,也可以在右上角查看监听的标签数据更新变化情况。
2.2.2 iottree.net lib 封装库调用说明
你可以把lib库放到你的客户端项目中,主要涉及3个封装类IOTTreeClient,IOTTreeTagVal,IOTTreeTag。下面是此lib库如何调用的说明。
IOTTreeClient是客户端主封装类。调用非常简单:
创建对象,并设置需要监听的标签路径列表:
cs
// create IOTTreeClient
client = new IOTTreeClient(url, clientid);
// tagpaths is List<string>
client.SetTagPaths(tagpaths);
由于标签路径是IOT-Tree Server项目中提供,你可以在IOT-Tree项目管理界面的标签列表进行导出复制,如图:

接下来我们就可以基于client对象提供的事件,进行监听并作后续处理:
cs
client.StateChanged += (sender, e) =>
{
ShowInf(false, $"State changed: {e.OldState} -> {e.NewState} ({e.Message})");
};
client.TagValueChanged += (sender, e) =>
{
// Console.WriteLine($"Tag updated: {e.TagPath} = {e.Value} at {e.UpdateTime}");
UpdateUI();
IOTTreeTagVal tagval = e.TagVal;
if (tagval.Path == "watertank.ch1.aio.wl_val")
{
UpdateWaterLvl(tagval);
}
if(tagval.Path== "watertank.ch1.dio.p_running")
{
//do something ....
}
};
client.ConnectionLost += (sender, e) =>
{
ShowInf(false, "Connection lost!");
};
client.ConnectionRestored += (sender, e) =>
{
ShowInf(false, "Connection restored!");
};
client.ErrorOccurred += (sender, e) =>
{
ShowInf(true, $"Error occurred: {e.Message}");
};
然后就可以启动或停止此client
cs
client.Start();
client.Stop();
client启动正常连接IOT-Tree Server之后,你才能使用以下函数:
cs
public IOTTreeTagVal GetTagValue(string tagPath);
public List<IOTTreeTagVal> GetTagValues();
public Dictionary<string, IOTTreeTagVal> GetTagValuesMap();
public List<IOTTreeTag> GetRegisterTags();
写或设置标签数据到IOT-Tree Server使用如下函数,这些函数都会触发单独的gRPC调用:
cs
//write value to tag,it may cause device driver to do write operation
public async Task<bool> WriteTagValueAsync(string tagPath, string value);
//set tag value in memory
public async Task<bool> SetTagValueAsync(string tagPath, string value)
从IOT-Tree Server查询项目列表和项目下面的标签列表函数(会触发单独的gRPC调用):
cs
public async Task<List<PrjItem>> ReadProjectListAsync() ;
public async Task<List<TagItem>> ReadTagsInProjectAsync(string projectName);
3 总结
从iottree.net提供的封装库Api看,整个调用过程非常简单。配合IOT-Tree Server的设备接入和数据组织,你可以把你的主要精力放在客户端展示功能的优化和美化上面------因为对客户端来说,围绕标签路径的就够了。