1 前言
長期以來,廣大程式員為到底是使用Client/Server,還是使用Browser/Server結構爭論不休,在這些爭論當中,C/S結構的程式可維護性差,布置困難,升級不方便,維護成本高就是一個相當重要的因素。有很多企業使用者就是因為這個原因而放棄使用C/S。然而當一個應用必須要使用C/S結構才能很好的實現其功能的時候,我們該如何解決用戶端的部署與自動升級問題?部署很簡單,只要點擊安裝程式即可,難的在於每當有新版本發布時,能夠實現自動升級。現在好了,我們的目標很簡單,我們希望開發一個與具體應用無關的能夠複用的自動升級系統。下面我為大家提供了一套可複用的用C#編寫的自動升級系統。
2 實現軟體的自動升級存在的困難
第一,為了尋找遠程伺服器上的更新,應用程式必須有查詢網路的途徑,這需要網路編程、簡單的應用程式與伺服器通訊的協議。
第二是下載。下載看起來不需要考慮連網的問題,但要考慮下載使用者請求的檔案,以及在沒有使用者同意時下載大檔案。友好的自動更新應用程式將使用剩餘的頻寬下載更新。這聽起來簡單,但卻是一個技術難題,幸運的是已經有瞭解決方法。
第三個考慮因素是使用新版應用程式更換原應用程式的過程。這個問題比較有趣,因為它要求代碼運行時將自己從系統刪除,有多種辦法可以實現該功能[5],本文程式主要通過比較新舊版本的日期號來實現替換新版本應用程式的功能。
3 實現軟體自動線上升級的原理
寫兩個程式,一個是主程式;一個是升級程式;所有升級任務都由升級程式完成。
1.啟動升級程式,升級程式串連到網站,下載新的主程式(當然還包括支援的庫檔案、XML配置文檔等)到臨時檔案夾;
2.升級程式擷取伺服器端XML設定檔中新版本程式的更新日期或版本號碼或檔案大小;
3.升級程式擷取原有用戶端應用程式的最近一次更新日期或版本號碼或檔案大小,兩者進行比較;如果發現升級程式的日期大於原有程式的最新日期,則提示使用者是否升級;或者是採用將現有版本與最新版本作比較,發現最新的則提示使用者是否升級;也有人用其它屬性如檔案大小進行比較,發現升級程式的檔案大小大於舊版本的程式的大小則提示使用者升級。本文主要採用比較新舊版本更新日期號來提示使用者升級。
4.如果使用者選擇升級,則擷取下載檔案清單,開始進行批量下載文檔;
5.升級程式檢測舊的主程式是否活動,若活動則關閉舊的主程式;
6.刪除舊的主程式,拷貝臨時檔案夾中的檔案到相應的位置;
7.檢查主程式的狀態,若狀態為活動的,則啟動新的主程式;
8.關閉升級程式,升級完成[4]。
4 用C#實現線上升級的關鍵步驟
這裡我主要使用日期資訊來檢測是否需要下載升級版本。
4.1 準備一個XML設定檔
名稱為AutoUpdater.xml,作用是作為一個升級用的模板,顯示需要升級的資訊。
<?xml version="1.0"?> //xml版本號碼
<AutoUpdater>
<URLAddres URL="http://192.168.198.113/vbroker/log/"/>//升級檔案所在伺服器端的網址
<UpdateInfo>
<UpdateTime Date = "2005-02-02"/> //升級檔案的更新日期
<Version Num = "1.0.0.1"/> //升級檔案的版本號碼
</UpdateInfo>
<UpdateFileList> //升級檔案清單
<UpdateFile FileName = "aa.txt"/> //共有三個檔案需升級
<UpdateFile FileName = "VB40.rar"/>
<UpdateFile FileName = "VB4-1.CAB"/>
</UpdateFileList>
<RestartApp>
<ReStart Allow = "Yes"/> //允許重新啟動應用程式
<AppName Name = "TIMS.exe"/> //啟動的應用程式名稱
</RestartApp>
</AutoUpdater>
從以上XML文檔中可以得知升級文檔所在伺服器端的地址、升級文檔的更新日期、需要升級的檔案清單,其中共有三個檔案需升級:aa.txt、VB40.rar、VB4-1.CAB。以及是否允許重新啟動應用程式和重新啟動的應用程式名稱。
4.2 擷取用戶端應用程式及伺服器端升級程式的最近一次更新日期
通過GetTheLastUpdateTime()函數來實現。
private string GetTheLastUpdateTime(string Dir)
{
string LastUpdateTime = "";
string AutoUpdaterFileName = Dir + @"\AutoUpdater.xml";
if(!File.Exists(AutoUpdaterFileName))
return LastUpdateTime;
//開啟xml檔案
FileStream myFile = new FileStream(AutoUpdaterFileName,FileMode.Open);
//xml檔案閱讀器
XmlTextReader xml = new XmlTextReader(myFile);
while(xml.Read())
{
if(xml.Name == "UpdateTime")
{
//擷取升級文檔的最後一次更新日期
LastUpdateTime = xml.GetAttribute("Date");
break;
}
}
xml.Close();
myFile.Close();
return LastUpdateTime;
}
通過XmlTextReader開啟XML文檔,讀取更新時間從而擷取Date對應的值,即伺服器端升級檔案的最近一次更新時間。
函數調用實現:
//擷取用戶端指定路徑下的應用程式最近一次更新時間
string thePreUpdateDate = GetTheLastUpdateTime(Application.StartupPath);
Application.StartupPath指用戶端應用程式所在的路徑。
//獲得從伺服器端已下載文檔的最近一次更新日期
string theLastsUpdateDate = GetTheLastUpdateTime(theFolder.FullName);
theFolder.FullName指在升級文檔下載到客戶機上的臨時檔案夾所在的路徑。
4.3 比較日期
用戶端應用程式最近一次更新日期與伺服器端升級程式的最近一次更新日期進行比較。
//獲得已下載文檔的最近一次更新日期
string theLastsUpdateDate = GetTheLastUpdateTime(theFolder.FullName);
if(thePreUpdateDate != "")
{
//如果用戶端將升級的應用程式的更新日期大於伺服器端升級的應用程式的更新日期
if(Convert.ToDateTime(thePreUpdateDate)>=Convert.ToDateTime(theLastsUpdateDate))
{
MessageBox.Show("當前軟體已經是最新的,無需更新!","系統提示",MessageBoxButtons.OK,MessageBoxIcon.Information);
this.Close();
}
}
this.labDownFile.Text = "下載更新檔案";
this.labFileName.Refresh();
this.btnCancel.Enabled = true;
this.progressBar.Position = 0;
this.progressBarTotal.Position = 0;
this.progressBarTotal.Refresh();
this.progressBar.Refresh();
//通過動態數組擷取下載檔案的列表
ArrayList List = GetDownFileList(GetTheUpdateURL(),theFolder.FullName);
string[] urls = new string[List.Count];
List.CopyTo(urls, 0);
將用戶端升級的應用程式的日期與伺服器端下載的應用程式日期進行比較,如果前者大於後者,則不更新;如果前者小於後者,則通過動態數組擷取下載檔案的列表,開始下載檔案。
通過BatchDownload()函數來實現。升級程式檢測舊的主程式是否活動,若活動則關閉舊的主程式;刪除舊的主程式,拷貝臨時檔案夾中的檔案到相應的位置;檢查主程式的狀態,若狀態為活動的,則啟動新的主程式。
private void BatchDownload(object data)
{
this.Invoke(this.activeStateChanger, new object[]{true, false});
try
{
DownloadInstructions instructions = (DownloadInstructions) data;
//批量下載
using(BatchDownloader bDL = new BatchDownloader())
{
bDL.CurrentProgressChanged += new DownloadProgressHandler(this.SingleProgressChanged);
bDL.StateChanged += new DownloadProgressHandler(this.StateChanged);
bDL.FileChanged += new DownloadProgressHandler(bDL_FileChanged);
bDL.TotalProgressChanged += new DownloadProgressHandler(bDL_TotalProgressChanged);
bDL.Download(instructions.URLs, instructions.Destination, (ManualResetEvent) this.cancelEvent);
}
}
catch(Exception ex)
{
ShowErrorMessage(ex);
}
this.Invoke(this.activeStateChanger, new object[]{false, false});
this.labFileName.Text = "";
//更新程式
if(this._Update)
{
//關閉原有的應用程式
this.labDownFile.Text = "正在關閉程式....";
System.Diagnostics.Process[]proc=System.Diagnostics.Process.GetProcessesByName("TIMS");
//關閉原有應用程式的所有進程
foreach(System.Diagnostics.Process pro in proc)
{
pro.Kill();
}
DirectoryInfo theFolder=new DirectoryInfo(Path.GetTempPath()+"JurassicUpdate");
if(theFolder.Exists)
{
foreach(FileInfo theFile in theFolder.GetFiles())
{
//如果臨時檔案夾下存在與應用程式所在目錄下的檔案同名的檔案,則刪除應用程式目錄下的檔案
if(File.Exists(Application.StartupPath + \\"+Path.GetFileName(theFile.FullName)))
File.Delete(Application.StartupPath + "\\"+Path.GetFileName(theFile.FullName));
//將臨時檔案夾的檔案移到應用程式所在的目錄下
File.Move(theFile.FullName,Application.StartupPath + \\"+Path.GetFileName(theFile.FullName));
}
}
//啟動安裝程式
this.labDownFile.Text = "正在啟動程式....";
System.Diagnostics.Process.Start(Application.StartupPath + "\\" + "TIMS.exe");
this.Close();
}
}
這段程式是實現線上升級的關鍵代碼,步驟有點複雜:首先用Invoke方法同步調用狀態改變進程,然後調用動態連結程式庫中批量下載(BatchDownloader.cs)類啟動批量下載,接著判斷原有的主應用程式有沒有關閉,如果沒關閉,則用Process.Kill()來關閉主程式。接下來,判斷臨時檔案夾下(即下載升級程式所在的目錄)是否存在與應用程式所在目錄下的檔案同名的檔案,如果存在同名檔案,則刪除應用程式目錄下的檔案,然後將臨時檔案夾的檔案移到應用程式所在的目錄下。最後重新啟動主應用程式。這樣更新就完成了。