在很多实际的项目开发中,我们使用了CS架构的开发方式,为什么需要使用CS架构设计?我们在开发企业级的应用软件 的时候,通常是伴随着网络通信服务的。大多数都是应用软件+数据库(或是上位机软件+实际PLC设备)
设计如下所示:
CS架构设计程序最大的好处,就是很多个客户端,可以提供一致的访问体验,可以部署到局域网的任意的电脑,只要网络保持畅通即可。比如说,登录之后,显示数据库的一些信息,或是显示PLC数据的信息。各个客户端都是显示服务器的数据的,因为想看数据的,大概率不是一个人而已。
但是上面的设计还有一定的问题:
1. 直接怼数据库,或是PLC,会导致安全性不高,通过反编译客户端的代码,可能可以轻松还原出数据库的连接字符串,或是PLC的IP地址,端口等信息,进而危害整个系统的安全性。
2. 客户端不容易实现自动更新的功能,如果已经分发了几十个几百个客户端,那么统一的更新升级,将是非常不容易的事。
那么怎么设计比较好呢?
正确的设计如上图所以,服务器程序独立出来,服务器承上启下,提供各种各样的接口给客户端,并提供客户端自动更新的实现支持。
好了,接下来的重点是如何实现你的自动更新的功能。
这时候需要使用一个组件和一个exe的更新程序来实现:
HslCommunication 软件,详细可以参考下面的地址:
github地址:https://github.com/dathlin/HslCommunication
官网:http://www.hslcommunication.cn
加群咨询学习信息:http://www.hslcommunication.cn/
在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装如果需要教程:Nuget安装教程:http://www.cnblogs.com/dathlin/p/7705014.html
Install-Package HslCommunication
组件的api地址:http://api.hslcommunication.cn
Upgrade.exe 软件,这是一个用于更新的软件
下载地址:http://www.hsltechnology.cn/Files/Upgrade.exe
简单的界面截图:
接下来说明如何使用自动更新的软件,这个 Upgrade 软件执行的操作及原理是什么呢?本质上就是连接远程服务器,将服务器指定目录下的所有文件(通常是客户端软件,一个软件通常是有个主exe程序,还有很多的dll,甚至可能包括很多数据文件)下载到本地的Upgrade程序所在的目录。
我们假设开发好的客户端软件长这个样子:
我们看到客户端有个exe,主启动程序名称为 HslCommunicationDemo.exe
好了,我们可以来写服务器的程序了,新建一个控制台的项目,并且从nuget安装hslcommunication组件
库安装好了,我们来写服务器支持更新的代码部分
internal class Program { static void Main( string[] args ) { // 实例化exe HslCommunication.Enthernet.NetSoftUpdateServer netSoftUpdate = new HslCommunication.Enthernet.NetSoftUpdateServer( "Upgrade.exe" ); // 客户端程序放在服务器当前目录的Client里面 netSoftUpdate.FileUpdatePath = System.IO.Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Client" ); netSoftUpdate.ServerStart( 12345 ); // 绑定的更新端口号信息 Console.ReadLine( ); } }
其实就是三行代码的事,服务器核心支持更新的代码已经写完成了,意思就是客户端的程序放到 目录 Client 下面。启动的端口为 12345。
我们把写好的客户端的程序放到了这个目录里,我们现在来初步验证下,当前的更新程序是否好使。怎么验证呢?
第一步我们先启动服务器端的程序。
启动之后,我们使用cmd来启动这个更新程序,因为还需要传入参数,所以不能直接双击打开这个exe
然后就可以看到,正在下载文件了。
下面的截图就是下载好了文件之后启动demo程序的例子。
好了,到这一步我们基本测试成功了上面的功能。这个更新程序是把服务器的目前下载到当前的目录里去。那么怎么样真正的实现更新呢?可以参考下面的流程信息
那么现在就是实现上述流程即可的,客户端启动时需要和服务器通信,并拿到版本号ID信息。版本号一般都是如下的方法
1、整数类型,版本 1,2,3,4,5,6,。。。。。。。 100000
2. 实数类型,1.0 , 1.1, 2.0, 10.0
3. 版本号信息 例如 1.0.0 1.0.1 2.0.0
这里关于如何取版本号,可以使用任何网络通信实现,哪怕是写socket也行。不过直接使用hslcommunication来实现,会更加的便捷。我们继续改造服务器的代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp4 { internal class Program { static void Main( string[] args ) { // 实例化exe HslCommunication.Enthernet.NetSoftUpdateServer netSoftUpdate = new HslCommunication.Enthernet.NetSoftUpdateServer( "Upgrade.exe" ); // 客户端程序放在服务器当前目录的Client里面 netSoftUpdate.FileUpdatePath = System.IO.Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Client" ); netSoftUpdate.ServerStart( 12345 ); // 绑定的更新端口号信息 // 实例化服务 Services services = new Services( ); HslCommunication.MQTT.MqttServer mqttServer = new HslCommunication.MQTT.MqttServer( ); // 注册服务 mqttServer.RegisterMqttRpcApi( services ); // 启动服务 mqttServer.ServerStart( 1883 ); Console.WriteLine( "启动成功!" ); Console.ReadLine( ); } /// <summary> /// 服务类,提供各种接口 /// </summary> public class Services { /// <summary> /// 获取客户端的版本号信息 /// </summary> /// <returns>版本号</returns> [HslCommunication.Reflection.HslMqttApi()] public string GetClientApi( ) { return Version; } public string Version { get; set; } = "1.0.0"; // 服务器上当前最新版本的客户端的版本号,这个值在你更新客户端版本后,就需要手动更新 } } }
这样就写好了服务器端的代码了,已经可以给到客户端的版本号了。那么我们能不能初步测试下接口到底行不行呢?当然可以的,使用 HslCommunicationDemo程序,可以进行便捷的测试,下载地址:
http://www.hsltechnology.cn/Home/Download
点击这个就可以下载到最新的demo版本(这个demo程序的版本更新就是使用上面的机制实现的),我们现在来验证一下,先打开服务器的程序,再打开demo测试界面:
那么我们就看到了结果
当然了,这个注册服务接口的功能,不仅仅用于传递版本号,实际上你可以从服务器获取任何的数据,都可以使用服务接口来实现的。对客户端是很简单的,那么我们回到客户端写代码!
按照上面的流程来说,客户端在启动的时候,就需要连接服务器,读取服务器的版本号,然后和当前的版本号做对比,如果发现有新版本更新,就启动当前目录的 Upgrade.exe 软件,好了,现在思路已经非常清晰了,来写代码
客户端启动就执行的话,我们一般会放到主窗体的load事件(或是主窗体实例化的事件里)里,当然如果你的客户端是初始界面一个支持登录操作的界面,可以在按钮点击登录时,连接服务器校验版本号。我们就以load窗体为例子。
private string currentVersion = "1.0.0"; private void FormLoad_Load( object sender, EventArgs e ) { HslCommunication.MQTT.MqttSyncClient client = new HslCommunication.MQTT.MqttSyncClient( new HslCommunication.MQTT.MqttConnectionOptions( ) { IpAddress = "127.0.0.1", // 服务器程序所在电脑的ip地址,这里是本机测试,就写 127.0.0.1 Port = 1883, // 端口,和服务器程序的配置一致 //Credentials = new HslCommunication.MQTT.MqttCredential("admin", "123456"), // 如果配置了用户名及密码的话,就是这么设置 } ); HslCommunication.OperateResult<string> readVersion = client.ReadRpc<string>( "Services/GetClientApi", new { } ); if (readVersion.IsSuccess) { // 获取成功,和当前的版本号对比 if( new HslCommunication.BasicFramework.SystemVersion(readVersion.Content) > new HslCommunication.BasicFramework.SystemVersion( currentVersion )) { // 有了新版本,当然你这里可以跳过确认,强制更新,此处演示 需要手动确认 if (MessageBox.Show("新版本:" + readVersion.Content + Environment.NewLine + "是否确认更新?", "新版", MessageBoxButtons.YesNo ) == DialogResult.Yes) { // 确认更新,启动当前目录的更新程序 try { System.Diagnostics.Process.Start( Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Upgrade.exe" ), "127.0.0.1 12345 \"HslCommunicationDemo.exe\"" ); System.Threading.Thread.Sleep( 20 ); Close( ); } catch (Exception ex) { MessageBox.Show( "启动更新失败:" + ex.Message ); Close( ); // 退出或是不退出 } } } // 如果是int类型的版本号 //if (int.Parse( readVersion.Content ) > int.Parse( currentVersion )) //{ //} } else { // 版本号获取失败,这里可以自行决定是否需要退出系统,如果需要退出,执行下面的Close // Close( ); } }
嗯,代码基本是写完了,逻辑也都弄好了。现在还差点什么东西呢,还差点新版本更新的流程。比如你得客户端界面调整优化了,重新弄了个版本,v1.0.1 首先这个地方就需要更改
将编译好的新的客户端程序,重新拷贝到服务器的Client目录里面。
然后修改服务器里的最新的版本号
这样就更新操作完成了,客户端在启动的时候,就会提供更新,点击了更新之后,就会自动启动更新程序了。但是这里还有个小细节,服务器每次更新版本号,岂不是要重启?可以控制台修改版本号,然后记录本地,服务器启动时加载本地的版本号即可。
代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ConsoleApp4 { internal class Program { static void Main( string[] args ) { // 实例化exe HslCommunication.Enthernet.NetSoftUpdateServer netSoftUpdate = new HslCommunication.Enthernet.NetSoftUpdateServer( "Upgrade.exe" ); // 客户端程序放在服务器当前目录的Client里面 netSoftUpdate.FileUpdatePath = System.IO.Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "Client" ); netSoftUpdate.ServerStart( 12345 ); // 绑定的更新端口号信息 // 实例化服务 Services services = new Services( ); string versionFile = System.IO.Path.Combine( AppDomain.CurrentDomain.BaseDirectory, "version.txt" ); // 检查本地版本号文件 if (System.IO.File.Exists( versionFile )) { services.Version = System.IO.File.ReadAllText( versionFile, Encoding.UTF8 ); } HslCommunication.MQTT.MqttServer mqttServer = new HslCommunication.MQTT.MqttServer( ); // 注册服务 mqttServer.RegisterMqttRpcApi( services ); // 启动服务 mqttServer.ServerStart( 1883 ); Console.WriteLine( "启动成功!" ); while (true) { // 手动输入更新,然后保存本地 string command = Console.ReadLine( ); if (command.StartsWith( "v:" )) { services.Version = command.Substring( 2 ); System.IO.File.WriteAllText( versionFile, services.Version, Encoding.UTF8 ); } } } /// <summary> /// 服务类,提供各种接口 /// </summary> public class Services { /// <summary> /// 获取客户端的版本号信息 /// </summary> /// <returns>版本号</returns> [HslCommunication.Reflection.HslMqttApi()] public string GetClientApi( ) { return Version; } public string Version { get; set; } = "1.0.1"; // 服务器上当前最新版本的客户端的版本号,这个值在你更新客户端版本后,就需要手动更新 } } }
当然如果你的是winform程序,还可以从界面上更新这个版本号信息。
还有几个小细节,
1. 你上传的客户端程序不应该包含一些敏感的数据,比如你的程序有登录账户的功能,会记录账户名,密码的配置文件,这个文件上传客户端目录的话,所有人就可以自动登录了。
2. 用于更新的 Upgrade.exe 会校验文件的md5,所以文件有修改才会下载到客户端的本地,所以不怕客户端的文件数量多。
3. 用于更新的 Upgrade.exe 会自动检测客户端电脑的语言环境,如果不是中文环境,界面会使用英文。
如果还有什么问题,可以通过最上方的连接找到作者。