你的位置:首页 > ASP.net教程

[ASP.net教程].Net remoting方法实现简单的在线升级(上篇:更新文件)


一、前言:

      最近做一个简单的在线升级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,其配置文件为:

<?"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.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     #endregion103   }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 UpdateLibrary13 {14   public class RemoteObject : MarshalByRefObject15   {   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);//转Json31       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.zip52       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,这里使用了backgroundWorker控件

 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       bgk_client.RunWorkerAsync(); 42     } 43  44     private void FormClient_FormClosed(object sender, FormClosedEventArgs e) 45     { 46       Environment.Exit(0); 47     } 48  49     private void bgk_client_DoWork(object sender, DoWorkEventArgs e) 50     { 51       52       //取得本地文件dll的版本号 53       Dictionary<string, string> localfileversion = new Dictionary<string, string>(); 54       foreach(string module in Config.Modules) 55       { 56         string filepath = System.IO.Path.Combine(Config.Dir,module+".dll"); 57         FileVersionInfo ver = FileVersionInfo.GetVersionInfo(filepath); 58         string dllVersion = ver.FileVersion; 59         localfileversion.Add(module, dllVersion);  //文件名-版本 60       } 61        62       //文件对比 63       try 64       { 65         RemoteObject remoteObject = (RemoteObject)Activator.GetObject(typeof(RemoteObject), string.Format("http://{0}/RemoteObject.rem", Config.ServerUrl)); 66         //调用remoting对象 67          68         string SFVersion = remoteObject.GetUpdateFileVersion();//获取服务器端更新文件的名称与版本号 69         List<string> Recieve = new List<string>(); 70         Recieve = JsonConvert.DeserializeObject<List<string>>(SFVersion);//转成泛型 71  72         Dictionary<string, string> serverfileversion = new Dictionary<string, string>();//转成字典型 73         for (int i = 0; i < Recieve.Count;i+=2 ) 74         { 75           serverfileversion.Add(Recieve[i],Recieve[i+1]); 76         } 77  78         if (serverfileversion.Count > 0) //是否有更新文件 79         { 80             foreach (var serverkey in serverfileversion.Keys) 81             { 82               if (localfileversion.ContainsKey(serverkey)) //本地是否有更新文件的名称,没有说明是新增的 83               { 84                 if (localfileversion[serverkey] == serverfileversion[serverkey]) //版本号相同? 85                 { 86                   serverfileversion.Remove(serverkey);//不需要更新 87                 } 88                 else 89                 { 90                   ConfirmUpdate.Add(serverkey, serverfileversion[serverkey]); //确认更新的 91                   Localupdate.Add(serverkey, localfileversion[serverkey]); //本地的版本 92                 } 93               } 94               else 95               { 96                 ConfirmAdd.Add(serverkey, serverfileversion[serverkey]);//确认新增文件,用于提示 97               } 98             } 99          }100          else101          {102             this.Close();103          }104         105       }106       catch (Exception ex)107       {108         e.Result = ex;109       }110     }111 112     private void bgk_client_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)113     {114       //Remoting连接出现问题时115       if (e.Result is Exception)116       {117         lblmessage.Text = (e.Result as Exception).Message;118         btn_update.Visible = false;119       }120       else121       {122         if(ConfirmAdd.Count==0 && ConfirmUpdate.Count==0)123         {124           lblmessage.Text = "没有需要更新的模块";125           btn_update.Visible = false;126         }127         else128         {129           string upinfo = string.Empty;130           lblmessage.Text = "检测完成,需要更新";131           btn_update.Enabled = true;132           //显示更新的133           if (ConfirmUpdate.Count>0)134           {135             upinfo = "更新文件信息:\r\n\r\n";136             foreach(var key in ConfirmUpdate.Keys)137             {138               upinfo += "文件名为:" + key + "\r\n" + "旧版本号:" + Localupdate[key] + "\r\n" + "新版本号:" + ConfirmUpdate[key] + "\r\n";139             }140           }141 142           //显示新增的143           if (ConfirmAdd.Count > 0)144           {145             upinfo += "\r\n";146             upinfo += "新增文件\r\n";147             foreach (var key in ConfirmAdd.Keys)148             {149               upinfo += "文件名为:" + key + "\r\n" + "版本号为:" + ConfirmAdd[key] + "\r\n";150               ConfirmUpdate.Add(key, ConfirmAdd[key]);151             }152           }         153           txt_UpdateMessage.Text = upinfo;         154         }155       }156     }157 158   }159 }

          

           第二个窗体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       else101       {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的方法,从而实现更新。