一、前言:
最近做一个简单的在线升级Demo,使用了微软较早的.Net Remoting技术来练手。
简单的思路就是在服务器配置一个Remoting对象,然后在客户端来执行Remoting对象中的方法。
过程:
(1) 读取本地dll文件的名称与版本号,与服务器的进行对比
(2) 确认需要升级的文件名称与版本号并告诉服务器,服务器将其复制到一个临时文件夹并压缩成zip
(3) 将服务器的zip下载到本地的临时文件夹,并解压。
定义服务器端为UpdateServer,其配置文件为:
<configuration> <system.runtime.remoting> <application> <service> <wellknown type="UpdateLibrary.RemoteObject, UpdateLibrary" mode="Singleton" objectUri="RemoteObject.rem"/> </service> <channels> <channel ref="http" port="8989"> </channel> </channels> </application> </system.runtime.remoting> <appSettings> <!--<add key="Dir" value="E:\server"/>--> </appSettings> </configuration>
定义客户端为UpdateClient,其配置文件为:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="ServerUrl" value="127.0.0.1:8989"/> <add key="Modules" value="BIMCoreDB"/> <add key="BufferLength" value="100"/> </appSettings> </configuration>
定义两端共同调用的dll为UpdateLibrary。
二、服务器端代码:
程序主入口:
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.Configuration; using UpdateLibrary; namespace UpdateServer { static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); LoadConfig(); Application.Run(new FormServer()); } private static void LoadConfig() { Config.Dir = System.IO.Path.Combine(Application.StartupPath, "serverfiles"); //更新包所在位置 Config.TempDir = System.IO.Path.Combine(Application.StartupPath, "temp"); //临时文件夹,用于放更新文件的地方。 } } }
服务器窗体后台代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Runtime.Remoting; namespace UpdateServer { public partial class FormServer : Form { public FormServer() { InitializeComponent(); try { //remoting配置 RemotingConfiguration.Configure(string.Format("{0}\\UpdateServer.exe.config", Application.StartupPath), false); } catch (Exception e) { MessageBox.Show(this, e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void FormServer_Load(object sender, EventArgs e) { lbl_time.Text = "当前时间:"+DateTime.Now.ToString("T"); tm_Server = new Timer(); tm_Server.Tick += tm_Server_Tick; tm_Server.Interval = 1000; tm_Server.Enabled = true; } void tm_Server_Tick(object sender,EventArgs e) { lbl_time.Text = string.Empty; lbl_time.Text = "当前时间:" + DateTime.Now.ToString("T"); } } }
三、UpdateLibrary:
UpdateLibrary类库包含三个类:
(1)Config类:用于提取配置文件中的信息。
(2)ZipHelper类:第三方库,用于文件压缩与解压缩。
(3)RemoteObject类:remoting对象,实现两端之间所需要的方法。
Config代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; namespace UpdateLibrary { /// <summary> /// 将配置文件中的信息传给Config对象 /// </summary> public static class Config { public static string Dir { get; set; } public static string TempDir { get; set; } public static string[] Modules { get; set; } public static int BufferLength { get; set; } public static string ServerUrl { get; set; } } }
ZipHelper代码:(比较实用的压缩与解压缩方法)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using ICSharpCode.SharpZipLib.Zip; 6 using ICSharpCode.SharpZipLib.Checksums; 7 using System.IO; 8 9 namespace UpdateLibrary 10 { 11 public class ZipHelper 12 { 13 #region 压缩 14 /// <summary> 15 /// 压缩文件 16 /// </summary> 17 /// <param name="sourceFilePath"></param> 18 /// <param name="destinationZipFilePath"></param> 19 public static void CreateZip(string sourceFilePath, string destinationZipFilePath) 20 { 21 if (sourceFilePath[sourceFilePath.Length - 1] != System.IO.Path.DirectorySeparatorChar) 22 sourceFilePath += System.IO.Path.DirectorySeparatorChar; 23 ZipOutputStream zipStream = new ZipOutputStream(File.Create(destinationZipFilePath)); 24 zipStream.SetLevel(6); // 压缩级别 0-9 25 CreateZipFiles(sourceFilePath, zipStream); 26 zipStream.Finish(); 27 zipStream.Close(); 28 } 29 /// <summary> 30 /// 递归压缩文件 31 /// </summary> 32 /// <param name="sourceFilePath">待压缩的文件或文件夹路径</param> 33 /// <param name="zipStream">打包结果的zip文件路径(类似 D:\WorkSpace\a.zip),全路径包括文件名和.zip扩展名</param> 34 /// <param name="staticFile"></param> 35 private static void CreateZipFiles(string sourceFilePath, ZipOutputStream zipStream) 36 { 37 Crc32 crc = new Crc32(); 38 string[] filesArray = Directory.GetFileSystemEntries(sourceFilePath); 39 foreach (string file in filesArray) 40 { 41 if (Directory.Exists(file)) //如果当前是文件夹,递归 42 { 43 CreateZipFiles(file, zipStream); 44 } 45 else //如果是文件,开始压缩 46 { 47 FileStream fileStream = File.OpenRead(file); 48 byte[] buffer = new byte[fileStream.Length]; 49 fileStream.Read(buffer, 0, buffer.Length); 50 string tempFile = file.Substring(sourceFilePath.LastIndexOf("\\") + 1); 51 ZipEntry entry = new ZipEntry(tempFile); 52 entry.DateTime = DateTime.Now; 53 entry.Size = fileStream.Length; 54 fileStream.Close(); 55 crc.Reset(); 56 crc.Update(buffer); 57 entry.Crc = crc.Value; 58 zipStream.PutNextEntry(entry); 59 zipStream.Write(buffer, 0, buffer.Length); 60 } 61 } 62 } 63 #endregion 64 65 #region 解压缩 66 67 public static void UnZip(Stream stream, string targetPath) 68 { 69 using (ZipInputStream zipInStream = new ZipInputStream(stream)) 70 { 71 ZipEntry entry; 72 while ((entry = zipInStream.GetNextEntry()) != null) 73 { 74 string directorName = Path.Combine(targetPath, Path.GetDirectoryName(entry.Name)); 75 string fileName = Path.Combine(directorName, Path.GetFileName(entry.Name)); 76 // 创建目录 77 if (directorName.Length > 0) 78 { 79 Directory.CreateDirectory(directorName); 80 } 81 if (fileName != string.Empty && !entry.IsDirectory) 82 { 83 var ext = System.IO.Path.GetExtension(fileName); 84 using (FileStream streamWriter = File.Create(fileName)) 85 { 86 int size = 4096; 87 byte[] data = new byte[4 * 1024]; 88 while (true) 89 { 90 size = zipInStream.Read(data, 0, data.Length); 91 if (size > 0) 92 { 93 streamWriter.Write(data, 0, size); 94 } 95 else break; 96 } 97 } 98 } 99 } 100 } 101 } 102 #endregion 103 } 104 }
RemoteObject代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Diagnostics; 5 using System.Text; 6 using System.IO; 7 using System.Collections; 8 using Newtonsoft.Json; 9 10 11 12 namespace UpdateLibrary 13 { 14 public class RemoteObject : MarshalByRefObject 15 { 16 public string GetUpdateFileVersion() 17 { 18 System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(Config.Dir); 19 System.IO.FileInfo[] files = dir.GetFiles("*.dll"); //获取服务器端所有dll文件 20 21 List<string> fileinfo = new List<string>();//记录文件名与文件版本 22 foreach(var file in files) 23 { 24 string filename = System.IO.Path.GetFileNameWithoutExtension(file.ToString());//获取文件名称 25 fileinfo.Add(filename); 26 FileVersionInfo ver = FileVersionInfo.GetVersionInfo(System.IO.Path.Combine(Config.Dir, file.ToString())); 27 string fileversion = ver.FileVersion; //获取文件的版本 28 fileinfo.Add(fileversion); 29 } 30 string SendData = JsonConvert.SerializeObject(fileinfo);//转Json 31 return SendData; 32 } 33 34 public IList CreateZipfile(string str_r) 35 { 36 List<string> templist = JsonConvert.DeserializeObject<List<string>>(str_r);//接收到确认更新的文件名 37 38 foreach (string filename in templist) //把需要更新的文件都复制到临时文件夹中 39 { 40 string updatefile = Path.Combine(Config.Dir, filename + ".dll"); 41 File.Copy(updatefile, Path.Combine(Config.TempDir, filename + ".dll"), true); 42 System.IO.File.SetAttributes(Path.Combine(Config.TempDir, filename + ".dll"), System.IO.FileAttributes.Normal);//去掉文件只读属性 43 } 44 45 string tempzippath=Path.Combine(Config.Dir,"tempzip");//临时压缩包路径,默认更新文件夹下的tempzip文件夹 46 if(Directory.Exists(tempzippath)==false) //判断是否有安放压缩包的地方 47 { 48 Directory.CreateDirectory(tempzippath); 49 } 50 51 ZipHelper.CreateZip(Config.TempDir, Path.Combine(tempzippath, "Update.zip"));//将临时文件夹内的文件都压缩到tempzip文件夹下的update.zip 52 System.IO.FileInfo f = new FileInfo(Path.Combine(tempzippath,"Update.zip"));//获得该压缩包的大小 53 IList SendData = new ArrayList(); 54 SendData.Add(Path.Combine(tempzippath, "Update.zip")); //得到压缩包名称 55 SendData.Add(f.Length); //得到压缩包文件大小 56 return SendData; 57 } 58 public byte[] GetFile(string name, int start, int length) 59 { 60 using (System.IO.FileStream fs = new System.IO.FileStream(System.IO.Path.Combine(Config.TempDir, name), System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite)) 61 { 62 byte[] buffer = new byte[length]; 63 fs.Position = start; 64 fs.Read(buffer, 0, length); 65 return buffer; 66 } 67 } 68 69 public void Finish(string name) 70 { 71 // File.Delete(System.IO.Path.Combine(Config.TempDir, name)); //删除压缩包文件夹 72 } 73 } 74 }
四、客户端端代码:
程序主入口:
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.Configuration; using UpdateLibrary; namespace UpdateClient { static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); LoadConfig(args); Application.Run(new FormClient()); } private static void LoadConfig(string[] args) { Config.Dir = System.IO.Path.Combine(Application.StartupPath,"localfiles");//本地文件位置 Config.TempDir = System.IO.Path.Combine(Application.StartupPath, "temp");//本地放更新文件的位置 Config.ServerUrl = ConfigurationManager.AppSettings["ServerUrl"].ToString();//设置服务器Url Config.Modules =ConfigurationManager.AppSettings["Modules"].ToString().Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);//更新文件的名称 Config.BufferLength = int.Parse(ConfigurationManager.AppSettings["BufferLength"].ToString()); //缓存大小 } } }
第一个窗体FormClient,用于比对文件,如果有更新则提供按钮进入更新窗体FormUpdate
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Diagnostics; 6 using System.Drawing; 7 using System.Linq; 8 using System.Text; 9 using System.Windows.Forms; 10 using UpdateLibrary; 11 using System.IO; 12 using Newtonsoft.Json; 13 14 namespace UpdateClient 15 { 16 public partial class FormClient : Form 17 { 18 Dictionary<string, string> Localupdate = new Dictionary<string, string>(); //确认本地需要文件 19 Dictionary<string, string> ConfirmUpdate = new Dictionary<string, string>(); //确认需要更新的文件并告诉服务器的 20 Dictionary<string, string> ConfirmAdd = new Dictionary<string, string>(); //确认需要新增的文件 21 public FormClient() 22 { 23 InitializeComponent(); 24 btn_update.Enabled = false; 25 int ScreenWidth = Screen.PrimaryScreen.WorkingArea.Width; 26 int ScreenHeight = Screen.PrimaryScreen.WorkingArea.Height; 27 int x = ScreenWidth - this.Width - 5; 28 int y = ScreenHeight - this.Height - 5; 29 this.Location = new Point(x, y); 30 } 31 32 private void btn_update_Click(object sender, EventArgs e) 33 { 34 Form updatewindow = new FormUpdate(ConfirmUpdate); 35 updatewindow.Show(); 36 this.Hide(); 37 } 38 39 private void FormClient_Load(object sender, EventArgs e) 40 { 41 Dictionary<string, string> localfileversion = new Dictionary<string, string>(); 42 foreach (string module in Config.Modules) 43 { 44 string filepath = System.IO.Path.Combine(Config.Dir, module + ".dll"); 45 FileVersionInfo ver = FileVersionInfo.GetVersionInfo(filepath); 46 string dllVersion = ver.FileVersion; 47 localfileversion.Add(module, dllVersion); //文件名-版本 48 } 49 50 //文件对比 51 try 52 { 53 RemoteObject remoteObject = (RemoteObject)Activator.GetObject(typeof(RemoteObject), string.Format("http://{0}/RemoteObject.rem", Config.ServerUrl)); 54 //获取服务器更新包的版本号 55 56 string SFVersion = remoteObject.GetUpdateFileVersion();//获取服务器端更新文件的名称与版本号 57 List<string> Recieve = new List<string>(); 58 Recieve = JsonConvert.DeserializeObject<List<string>>(SFVersion);//转成泛型 59 Dictionary<string, string> serverfileversion = new Dictionary<string, string>();//转成字典型 60 for (int i = 0; i < Recieve.Count; i += 2) 61 { 62 serverfileversion.Add(Recieve[i], Recieve[i + 1]); 63 } 64 65 if (serverfileversion.Count > 0) //是否有更新文件 66 { 67 foreach (var serverkey in serverfileversion.Keys) 68 { 69 if (localfileversion.ContainsKey(serverkey)) //本地是否有更新文件的名称,没有说明是新增的 70 { 71 if (localfileversion[serverkey] == serverfileversion[serverkey]) //版本号相同? 72 { 73 serverfileversion.Remove(serverkey);//不需要更新 74 } 75 else 76 { 77 ConfirmUpdate.Add(serverkey, serverfileversion[serverkey]); //确认更新的 78 Localupdate.Add(serverkey, localfileversion[serverkey]); //本地的版本 79 } 80 } 81 else 82 { 83 ConfirmAdd.Add(serverkey, serverfileversion[serverkey]);//确认新增文件,用于提示 84 } 85 } 86 } 87 else 88 { 89 lblmessage.Text = "暂无更新文件"; 90 btn_update.Visible = false; 91 } 92 } 93 catch(Exception ex) 94 { 95 lblmessage.Text = ex.ToString(); 96 btn_update.Visible = false; 97 } 98 99 if(ConfirmAdd.Count==0 && ConfirmUpdate.Count==0) 100 { 101 lblmessage.Text = "没有需要更新的模块"; 102 btn_update.Visible = false; 103 } 104 else 105 { 106 string upinfo = string.Empty; 107 lblmessage.Text = "检测完成,需要更新"; 108 btn_update.Enabled = true; 109 //显示更新的 110 if (ConfirmUpdate.Count>0) 111 { 112 upinfo = "更新文件信息:\r\n\r\n"; 113 foreach(var key in ConfirmUpdate.Keys) 114 { 115 upinfo += "文件名为:" + key + "\r\n" + "旧版本号:" + Localupdate[key] + "\r\n" + "新版本号:" + ConfirmUpdate[key] + "\r\n"; 116 } 117 } 118 119 //显示新增的 120 if (ConfirmAdd.Count > 0) 121 { 122 upinfo += "\r\n"; 123 upinfo += "新增文件\r\n"; 124 foreach (var key in ConfirmAdd.Keys) 125 { 126 upinfo += "文件名为:" + key + "\r\n" + "版本号为:" + ConfirmAdd[key] + "\r\n"; 127 ConfirmUpdate.Add(key, ConfirmAdd[key]); 128 } 129 } 130 txt_UpdateMessage.Text = upinfo; 131 } 132 133 } 134 private void FormClient_FormClosed(object sender, FormClosedEventArgs e) 135 { 136 Environment.Exit(0); 137 } 138 139 140 } 141 }
第二个窗体FormUpdate,用于更新文件,这里同样使用了backgroundWorker控件来进行异步操作。
1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Windows.Forms; 9 using UpdateLibrary; 10 using Newtonsoft.Json; 11 using System.Collections; 12 using System.IO; 13 14 15 namespace UpdateClient 16 { 17 public partial class FormUpdate : Form 18 { 19 Dictionary<string, string> serverupdatefiles = new Dictionary<string, string>(); 20 public FormUpdate(Dictionary<string, string> confirmupdate) 21 { 22 InitializeComponent(); 23 int ScreenWidth = Screen.PrimaryScreen.WorkingArea.Width; 24 int ScreenHeight = Screen.PrimaryScreen.WorkingArea.Height; 25 int x = ScreenWidth - this.Width - 5; 26 int y = ScreenHeight - this.Height - 5; 27 this.Location = new Point(x, y); 28 serverupdatefiles = confirmupdate; //获得需要更新的列表 29 } 30 31 private void FormUpdate_Load(object sender, EventArgs e) 32 { 33 bgk_Update.RunWorkerAsync() ; 34 } 35 36 private void FormUpdate_FormClosed(object sender, FormClosedEventArgs e) 37 { 38 Environment.Exit(0); //终止该进程 39 } 40 41 private void bgk_Update_DoWork(object sender, DoWorkEventArgs e) 42 { 43 try 44 { 45 RemoteObject remoteObject = (RemoteObject)Activator.GetObject(typeof(RemoteObject), string.Format("http://{0}/RemoteObject.rem", Config.ServerUrl)); 46 bgk_Update.ReportProgress(0, "准备更新..."); 47 48 List<string> list_temp = new List<string>(); 49 list_temp = DictToList(serverupdatefiles);//将确认更新的文件名转成泛型 50 string str_confirmupdate = JsonConvert.SerializeObject(list_temp);//转成Json 51 52 //将确认的文件列表返回给server,使其压缩并放置在temp文件夹下 53 IList RecieveData = new ArrayList(); 54 RecieveData = remoteObject.CreateZipfile(str_confirmupdate); 55 56 string fileName = RecieveData[0].ToString(); //获得压缩包的名称 57 int fileLength = Convert.ToInt32(RecieveData[1]);//获得压缩包的大小 58 59 string filePath = Path.Combine(Config.TempDir,"Update.zip");//解压到本地临时文件夹下的Update.zip 60 61 using (System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create)) 62 { 63 for (int i = 0; i < fileLength; i += Config.BufferLength * 1024) 64 { 65 var percent = (int)((double)i / (double)fileLength * 100); 66 bgk_Update.ReportProgress(percent, "正在下载更新包..."); 67 int length = (int)Math.Min(Config.BufferLength * 1024, fileLength - i); 68 var bytes = remoteObject.GetFile(fileName, i, length); 69 stream.Write(bytes, 0, length); 70 } 71 stream.Flush(); 72 } 73 remoteObject.Finish(fileName); 74 bgk_Update.ReportProgress(100, "正在解压"); 75 using (System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)) 76 { 77 ZipHelper.UnZip(stream, Config.TempDir);//解压获得更新文件 78 } 79 System.IO.File.Delete(filePath);//删除解压包 80 e.Result = "更新完成"; 81 } 82 catch (Exception ex) 83 { 84 e.Result = ex; 85 } 86 } 87 88 private void bgk_Update_ProgressChanged(object sender, ProgressChangedEventArgs e) 89 { 90 lbl_message.Text = (string)e.UserState; 91 pbr_Update.Value = e.ProgressPercentage; 92 } 93 94 private void bgk_Update_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 95 { 96 if (e.Result is Exception) 97 { 98 lbl_message.Text = (e.Result as Exception).Message; 99 } 100 else 101 { 102 lbl_message.Text = (string)e.Result; 103 } 104 } 105 106 public List<string> DictToList(Dictionary<string,string> dict) 107 { 108 List<string> templist = new List<string>(); 109 foreach(var key in dict.Keys) 110 { 111 templist.Add(key); 112 } 113 return templist; 114 } 115 } 116 }
五、小结:
(1) 运用了Remoting技术,简单来说就是服务器通过config文件配置remoting服务;客户端调用这个remoting服务;
(2) 使用了BackgroundWorker控件,其主要是三种方法Dowork();ProgressChanged();RunWorkerCompleted()
其中控件的ReportProgress()方法可以通过ProgressChanged中的label与ProgressBar来显示升级状况与进度。
(3) 上篇以remoting方式叙述如何从服务器端下载文件到本地,下篇将介绍得到更新的dll后如何将调用旧dll的exe程序关闭再重启加载新dll的方法,从而实现更新。