C/S程序自動升級是一個很重要的功能,原理其實很簡單,一般包含兩個程序一個是主程序,也就是除了升級功能以外的程序,另一個就是升級程序,常見的360,金山安全衛士都是這樣。
主要包括以下幾點: 1 比較版本 2下載文件 3更新文件 4啟動主程序。但其中的需要注意的細節很多。
一般服務端會有一個配置文件包含最新更新的文件信息的配置文件,當然這些更新信息也可以存到數據庫,或者其他地方。客戶端(也就是需要更新的那部分程序)也有一個配置文件包含客戶端版本信息,這些信息可以存到專門的一個配置文件中,或者是config文件中,沒有一定的規定,可以根據實際設計。
在客戶端程序啟動時,先啟動更新程序通過比較本地版本和服務端最新的版本信息判斷是否有新版本,如果有可以直接下載,下載完成替換成功后並更新客戶端版本信息,啟動主程序。
缺點:如果更新速度由於更新的文件很大或者網速很慢,用戶不得不等待很長時間,直到下載完成或者下載失敗。
優點:處理完成后,啟動的直接就是更新后的程序。不會出現由於主程序在運行導致替換文件時提示文件在使用,不能替換之類的錯誤。
另一種方法是, 在客戶端段程序啟動時,啟動更新程序,但更新程序不做版本判斷,到客戶端更新目錄下檢查有沒有下載的新版本,如果有就更新主程序並更新客戶端版本信息,然后啟動主程序,如果沒有就直接啟動主程序。由主程序判斷是否有新版本,並在后台下載把文件放到客戶端更新目錄中,下載完成后,提示用戶退出主程序,重新啟動,在啟動時由更新程序並更新客戶端和客戶端版本信息。
缺點:由於下載是在主程序的后台運行,可能會影響主程序的處理速度。
優點:避免了由於下載導致用戶長時間的等待。
1 比較版本
比較依據:
可以通過文件的最后修改時間,或者使用文件版本作為比較依據,使用文件最后修改時間顯然不是標准的做法,但也沒有錯誤,但需要注意日期的格式一定要統一,避免日 期格式不一致導致錯誤。可以使用Fileinfo類獲取最后修改時間,注意時間應該取服務器時間,編譯程序集的機器時間應該相同,否則可能導致混亂。
使用文件版本作為標准,則每次修改時必須修改版本號,C#程序就是要修AssemblyInfo.cs文件中的內容了,多了一步,規范多了。Version類處理版本信息並比較。
- Assembly thisAssem = Assembly.GetExecutingAssembly();
- AssemblyName thisAssemName = thisAssem.GetName();
- Version ver = thisAssemName.Version;
當然也有其他的方式,例如MD5校驗值比較,文件大小比較,之類的方法。不過個人認為文件大小缺陷很明顯,新版本文件就一定比舊文件大嗎?不一定吧。重構是可能變小的。
當然如果考慮客戶端有不同的版本,都需要升級到最新的版本,顯然不同的版本對應的升級文件不同,會更復雜,比較的信息也更多。
獲取服務端版本信息:
如果服務端的版本信息存在數據庫,直接讀取數據庫,就可以獲取。如果存在配置文件,則可以通過webservice方法獲取,或者請求一個網頁 通過Response.Write();的方式獲取信息,當然這兩種方式都要建立虛擬目錄或者網站。
2下載文件
存儲位置:
如果新版本的文件存在數據庫,就直接讀取數據庫,不過這種方式個人不建議使用,例如更新文件很大時性能不是很好。
存在固定虛擬目錄的指定路徑下,不失為一種好的方式,但客戶端要下載,所以要注意一定要分配下載權限。
下載方式:
直接向通過虛擬路徑發出請求,下載文件,由於虛擬路徑有下載權限,如果更新需要判斷是否有權限,例如要交費后才能下載則不好處理。
另一種方式是向一個網頁發送請求,傳遞不同的查詢字符串,網頁 通過Response.BinaryWrite();的方式下載文件,則可以判斷權限,當然麻煩一些是避免不了的。
下載文件代碼
- 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啟動主程序
主程序路徑的獲取:
相對路徑 主程序,更新程序,都使用相對路徑,缺點是一旦相對路徑確定后,后續的更新就不能更改這種目錄關系。
注冊表 路徑都存入注冊表,需要時通過注冊表交互,主程序寫注冊表,更新程序讀取注冊表,缺點是讀寫注冊表需要權限,寫的路徑也要固定,后續的更新不能改變寫在注冊表中的位置,也就是注冊表路徑。
運行程序代碼
- 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;
- }
- }
- }