C/S程序自动升级是一个很重要的功能,原理其实很简单,一般包含两个程序一个是主程序,也就是除了升级功能以外的程序,另一个就是升级程序,常见的360,金山安全卫士都是这样。
主要包括以下几点: 1 比较版本 2下载文件 3更新文件 4启动主程序。但其中的需要注意的细节很多。
一般服务端会有一个配置文件包含最新更新的文件信息的配置文件,当然这些更新信息也可以存到数据库,或者其他地方。客户端(也就是需要更新的那部分程序)也有一个配置文件包含客户端版本信息,这些信息可以存到专门的一个配置文件中,或者是config文件中,没有一定的规定,可以根据实际设计。
在客户端程序启动时,先启动更新程序通过比较本地版本和服务端最新的版本信息判断是否有新版本,如果有可以直接下载,下载完成替换成功后并更新客户端版本信息,启动主程序。
缺点:如果更新速度由于更新的文件很大或者网速很慢,用户不得不等待很长时间,直到下载完成或者下载失败。
优点:处理完成后,启动的直接就是更新后的程序。不会出现由于主程序在运行导致替换文件之类的错误。
另一种方法是, 在客户端段程序启动时,启动更新程序,但更新程序不做版本判断,到客户端更新目录下检查有没有下载的新版本,如果有就更新主程序并更新客户端版本信息,然后启动主程序,如果没有就直接启动主程序。由主程序判断是否有新版本,并在后台下载把文件放到客户端更新目录中,下载完成后,提示用户退出主程序,重新启动,在启动时由更新程序并更新客户端和客户端版本信息。
缺点:由于下载是在主程序的后台运行,可能会影响主程序的处理速度。
优点:避免了由于下载导致用户长时间的等待。
1 比较版本
比较依据:
可以通过文件的最后修改时间,或者使用文件版本作为比较依据,使用文件最后修改时间显然不是标准的做法,但也没有错误,但需要注意日期的格式一定要统一,避免日 期格式不一致导致错误。可以使用Fileinfo类获取最后修改时间,注意时间应该取服务器时间,编译程序集的机器时间应该相同,否则可能导致混乱。
FIleInfo类官网参考
使用文件版本作为标准,则每次修改时必须修改版本号,C#程序就是要修AssemblyInfo.cs文件中的内容了,多了一步,规范多了。Version类处理版本信息并比较。
view plain copy to clipboard print ?
- Assembly thisAssem = Assembly.GetExecutingAssembly();
- AssemblyName thisAssemName = thisAssem.GetName();
- Version ver = thisAssemName.Version;
Assembly thisAssem = Assembly.GetExecutingAssembly(); AssemblyName thisAssemName = thisAssem.GetName(); Version ver = thisAssemName.Version;
Version类官网参考
当然也有其他的方式,例如MD5校验值比较,文件大小比较,之类的方法。不过个人认为文件大小缺陷很明显,新版本文件就一定比旧文件大吗?不一定吧。重构是可能变小的。
当然如果考虑客户端有不同的版本,都需要升级到最新的版本,显然不同的版本对应的升级文件不同,会更复杂,比较的信息也更多。
获取服务端版本信息:
如果服务端的版本信息存在数据库,直接读取数据库,就可以获取。如果存在配置文件,则可以通过webservice方法获取,或者请求一个网页 通过Response.Write();的方式获取信息,当然这两种方式都要建立虚拟目录或者网站。
2下载文件
存储位置:
如果新版本的文件存在数据库,就直接读取数据库,不过这种方式个人不建议使用,例如更新文件很大时性能不是很好。
存在固定虚拟目录的指定路径下,不失为一种好的方式,但客户端要下载,所以要注意一定要分配下载权限。
下载方式:
直接向通过虚拟路径发出请求,下载文件,由于虚拟路径有下载权限,如果更新需要判断是否有权限,例如要交费后才能下载则不好处理。
另一种方式是向一个网页发送请求,传递不同的查询字符串,网页 通过Response.BinaryWrite();的方式下载文件,则可以判断权限,当然麻烦一些是避免不了的。
下载文件代码
view plain copy to clipboard print ?
- Uri uri = new Uri(downFileUrl + localFileName);
- HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
- request.Credentials = CredentialCache.DefaultCredentials;
- request.MaximumAutomaticRedirections = 4;
- localFileName = Path.GetFileName(localFileName);
- using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
- {
- Stream receiveStream = response.GetResponseStream();
- string newPath = Path.Combine(tempFold, localFileName);
- using (FileStream fs = new FileStream(newPath, FileMode.Create))
- {
- Byte[] buffer = new Byte[4096];
- int bytesRead = receiveStream.Read(buffer, 0, buffer.Length);
- while (bytesRead > 0){
- fs.Write(buffer, 0, bytesRead);
- bytesRead = receiveStream.Read(buffer, 0, buffer.Length);
- }
- }
- receiveStream.Close();
- }
Uri uri = new Uri(downFileUrl + localFileName); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); request.Credentials = CredentialCache.DefaultCredentials; request.MaximumAutomaticRedirections = 4; localFileName = Path.GetFileName(localFileName); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { Stream receiveStream = response.GetResponseStream(); string newPath = Path.Combine(tempFold, localFileName); using (FileStream fs = new FileStream(newPath, FileMode.Create)) { Byte[] buffer = new Byte[4096]; int bytesRead = receiveStream.Read(buffer, 0, buffer.Length); while (bytesRead > 0){ fs.Write(buffer, 0, bytesRead); bytesRead = receiveStream.Read(buffer, 0, buffer.Length); } } receiveStream.Close(); }
3更新文件
更新类型:
直接替换的,例如修改了bug,直接替换的。
新增加的,例如新增加的功能做成了新的类库。
需要删除的,例如有些功能由于重构或者使用了了新方法不需要的。
需要执行的,例如写注册表,注册COM组件的。
每一种处理方式都不一样,需要根据类型分开处理
缺点:升级后,没办法取消升级,像windows的补丁程序可以安装,可以卸载的原理,目前还没有研究明白,希望知道的牛人指导。
当然也可以简单的先卸载,再安装,对于配置文件之类的信息特殊处理一下也可以。
当然如果考虑客户端有不同的版本,都需要升级到最新的版本,显然不同的版本对应的升级文件不同,会更复杂,但基本原理却不变。
4启动主程序
主程序路径的获取:
相对路径 主程序,更新程序,都使用相对路径,缺点是一旦相对路径确定后,后续的更新就不能更改这种目录关系。
注册表 路径都存入注册表,需要时通过注册表交互,主程序写注册表,更新程序读取注册表,缺点是读写注册表需要权限,写的路径也要固定,后续的更新不能改变写在注册表中的位置,也就是注册表路径。
运行程序代码
view plain copy to clipboard print ?
- private static void RunFile(string dir, string localFileName){
- string info = "运行程序" + localFileName;
- try{
- if (File.Exists(Path.Combine(dir, localFileName))){
- Process myProcess = new Process();
- ProcessStartInfo psi = new ProcessStartInfo();
- psi.FileName = localFileName;
- psi.WorkingDirectory = dir;
- psi.UseShellExecute = false;
- psi.RedirectStandardError = true;
- psi.CreateNoWindow = true;
- psi.RedirectStandardOutput = true;
- psi.WindowStyle = ProcessWindowStyle.Hidden;
- myProcess.StartInfo = psi;
- myProcess.Start();
-
- string error = myProcess.StandardError.ReadToEnd();
- string output = myProcess.StandardOutput.ReadToEnd();
- myProcess.WaitForExit();
- myProcess.Close();
- if (error != string.Empty){
- Log.Write("StandardError:" + error);
- }
- if (output != string.Empty){
- Log.Write("StandardOutput:" + output);
- }
- Log.LogProcessEnd(info);
- }
- }
- catch (Exception ex){
- Log.Write(info + "出错");
- Log.LogException(ex);
- throw ex;
- }
- }
- }
private static void RunFile(string dir, string localFileName){ string info = "运行程序" + localFileName; try{ if (File.Exists(Path.Combine(dir, localFileName))){ Process myProcess = new Process(); ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = localFileName; psi.WorkingDirectory = dir; psi.UseShellExecute = false; psi.RedirectStandardError = true; psi.CreateNoWindow = true; psi.RedirectStandardOutput = true; psi.WindowStyle = ProcessWindowStyle.Hidden; myProcess.StartInfo = psi; myProcess.Start(); string error = myProcess.StandardError.ReadToEnd(); string output = myProcess.StandardOutput.ReadToEnd(); myProcess.WaitForExit(); myProcess.Close(); if (error != string.Empty){ Log.Write("StandardError:" + error); } if (output != string.Empty){ Log.Write("StandardOutput:" + output); } Log.LogProcessEnd(info); } } catch (Exception ex){ Log.Write(info + "出错"); Log.LogException(ex); throw ex; } } } 源代码下载