C#.Net版本自動更新程序及3種策略實現
C/S程序是基於客戶端和服務器的,在客戶機編譯新版本后將文件發布在更新服務器上,然后建立一個XML文件,該文件列舉最新程序文件的版本號及最后修改日期。如程序文件較多的話可以通過工具自動生成XML文件。當某客戶機運行程序后會自動下載這個XML文件,通過與本地機器上的版本號匹配,如本機上的版本號比服務器上的要舊,通知客戶機運行更新程序。如用戶更新了版本,將最新版本號寫入配置文件,這樣方便下一次匹配。
通過可行性分析可以使用下面3種方案下載
1. 局域網共享文件夾下載
2. Tcp/ip遠程下載
3. 通過Web方式下載
1.局域網共享文件夾下載
方式1適合局域網絡,功能簡單,通過File.Copy()方式從服務器直接復制文件。如建立企業級的VPN網絡,也可作為局域網絡直接復制。共享文件夾下載實現非常簡單,我們只需要在服務器上共享一個文件夾並設定訪問權限,然后將最新版本存放在這個目錄,升級程序直接從這個目錄Copy文件即可。
2.Tcp/ip遠程下載
方式2是通過基於tcp/ip 的Socket組件編程來實現,這個機制必須要建立服務器用於監聽客戶的升級請求。簡單設計思路是在服務器端啟動TcpListener監聽客戶端的Socket連接,當Client發送連接請求,TcpListener捕獲當前請求的Socket,並取到請求命令數據(字符串),然后由命令處理程序分析該字符串,如果字符串頭部包含GET_FILE標識則為下載文件。舉例說明:客戶機向服務器程序發送請求命令:"GET_FILE|D:\PUBLISH\TEST.DLL"。首先TcpListener捕獲當前Socket.並接收到命令數據"GET_FILE|D:\PUBLISH\TEST.DLL",通過分析程序發現"GET_FILE"是特殊命令,表示下載文件請求。然后通過socket.SendFile(file="D:\PUBLISH\TEST.DLL")將文件傳送給當前Socket。客戶端由NetworkStream.Read()方法接收由服務器傳來的文件。
3.通過Web方式下載。
方式3是通過.NetFramework提供的WebClient組件下載文件。只需指定DownloadData()方法中參數address(url)。
下面講解版本更新程序系統框架圖:
主窗體<->下載控制器<->XmlLoader關系圖
圖解:
frmUpgrader窗體定義一個下載控制器及2個TreeView組件。
當點[檢查更新]按鈕,控制器調用當前下載器的DownloadServerXml()方法從服務器下載XmlServerFiles.xml文件。
下載后將文件交給XmlLoader分析,分析器創建XmlDocument實例。最后將XML分析器作為FileView構造器參數創建FileView實例。
FileView兩個重要方法:
LoadTreeViewClient()方法創建客戶端文件清單的TreeView。
LoadTreeViewServer()方法創建服務器端文件清單的TreeView。
TreeView的數據來源是兩個Xml文件。
DownloadController 下載控制器,它負責建立下載策略實例及控制當前的下載器。
XmlLoader分析器主要功能是分析服務器端及本地的XML文件(XmlServerFiles.xml和XmlClientFiles.xml)。XmlLoader類圖列舉了所有方法,從圖中可以看出,XmlLoader控制XmlDocument對象。通過XmlDocument.SelectSingleNode方法查找某個指定的文件,然后獲取文件最后修改日期文件名等信息用於匹配。
IDownloader接口定義了下載器的主要方法,下面會詳細講解3個下載器的實現策略。
FileInfo是文件的實體類,結構相當簡單,只包含文件名,物理路徑及最后修改時間。
三種下載器實現:
UML圖說明:
frmUpgrader: 主窗體
DownloadController:下載控制器,如上圖所示,它負責控制實現IDownloader接口的實例。
IDownloader: 下載器接口,下載器需要實現這個接口。
LAN_Downloader: 局域網復制文件下載器。
WebClient_Downloader: 廣域網下載器,通過WebClient組件下載文件。
TcpIp_Downloader: Tcp/ip下載器。需要運行Tcp/ip服務器提供下載服務。
主窗體有[檢查更新]及[開始更新]兩個按鈕。分別調用下載控制器的CheckUpdate()及Download()方法。
Tcp/IP下載器圖解:
Tcp/IP下載器需要有服務器支持,使用tcp/ip傳送文件簡單設計思路是:在服務器端啟動TcpListener監聽客戶端的Socket連接,當Client發送連接請求,TcpListener捕獲當前請求的Socket,並取到請求命令數據(字符串),然后由命令處理程序分析該字符串,如果字符串頭部包含GET_FILE標識則為下載文件。
UpgraderServer 是tcp/ip服務器的核心類。他控制TcpListener對象,TcpListener負責監聽客戶端的Socket連接。
當有下載文件請求時就調用SendFile()方法將文件傳送給當前連接的Socket.
Stop()方法用來關閉服務器.
SendFile()方法用來發送文件
StartListening()方法用戶啟動監聽程序。
TcpListener是監聽程序,它負責監聽客戶端的Socket連接。如有連接請求觸發AccecptSocket事件。該事件返回當前請求的Socket對象。
UpgraderClient是tcp/ip客戶端的核心類。他控制TcpClient對象, TcpClient對象負責監聽來自服務器的請求。
DownloadFile()方法詳解:
要明白客戶端是如何接收文件,先要明白NetworkStream對象. NetworkStream是提供用於網絡訪問的基礎數據流。客戶機監聽來自服務器的數據是通過NetworkStream.Read()方法實現的,當程序執行到ns.Read()方法時就開始監聽,請看代碼。
int resSize; //當前接收到的數據長度
do
{
//開始監聽,同時中斷下面代碼執行,直到接收到數據才會執行Read()下面的代碼。
resSize = ns.Read(resBytes, 0, resBytes.Length);
string msg = Byte2Str(resBytes);
if (msg.Trim().ToUpper() == "FILE_NOT_FOUND")
{
if (_writeOutput != null) _writeOutput("找不到文件:" + file);
break;
}
if (resSize == 0) break;
ms.Write(resBytes, 0, resSize);
} while (ns.DataAvailable);
ns.Close();
請注意while (ns.DataAvailable)這段代碼,當接受到來自服務器的數據時DataAvailable=True,然后通過NetworkStream.Read方法每次讀取256字節,直到讀取完所有數據時DataAvailable=false。這時監聽工作完成,跳出while循環。最后調用FileStream對象保存文件。
TcpIp_Downloader Tcp/IP下載器方法:
Download():下載XmlServerFiles.xml定義的所有文件。
DownloadFile(FileInfo file):下載單個文件。
DownloadServerXml():下載服務器上的文件清單。
Init() //初始化下載器。
IDownloader下載器接口定義
/// 下載器接口
/// </summary>
public interface IDownloader
{
void Init(); //初始化下載器
void Download(); //下載所有文件
FileInfo DownloadFile(FileInfo file); //下載單個文件
XmlLoader XmlServer { get;} //服務器上的Xml文件
XmlLoader XmlLocal { get ;}//客戶機上的Xml文件
int DownloadsCount { get;} //下載成功的文件總數
int DownloadFaliedCount { get ;}//下載失敗的文件總數
void DownloadServerXml(); //下載服務器上的文件清單(xml文件)
void SetProgressBar(ToolStripProgressBar progress);
void SetTrace(ListBox logList);
}
下載器類型
/// 下載器類型
/// </summary>
public enum DownloadType
{
Intranet = 1,
TcpIp = 2,
WebDownload = 3
}
下載控制器,該控件器可以創建3種不同的下載器。
/// 下載控制器,該控件器可以創建3種不同的下載器。
/// 策略模式應用。
/// </summary>
public class DownloadController
{
private IDownloader _downloader = null;
public IDownloader CurrentDownloader { get { return _downloader; } }
private TreeView _tvServerFiles;
private TreeView _tvLocalFiles;
private ListBox _log;
private ToolStripProgressBar _progress = null;
public DownloadController(IDownloader downloader)
{
_downloader = downloader;
}
/// <summary>
/// 跟據下載類型創建3種不同的下載策略
/// </summary>
public static DownloadController Create(DownloadType type)
{
if (DownloadType.Intranet == type)
return new DownloadController( new LAN_Downloader());
if (DownloadType.TcpIp == type)
return new DownloadController( new TcpIp_Downloader());
if (DownloadType.WebDownload == type)
return new DownloadController( new WebClient_Downloader());
return null;
}
public void Download()
{
_log.Items.Add("開始下載....");
_downloader.SetProgressBar(_progress);
_downloader.Init();
_downloader.SetTrace(_log);
_downloader.Download();
_log.Items.Add("下載完成....");
_log.Items.Add("刷新文件列表....");
//下載完成更新視圖
new FileView(_downloader.XmlLocal, _progress).LoadTreeViewClient(_tvLocalFiles, null);
_log.Items.Add("完成.");
}
public void CheckUpdate()
{
_log.Items.Add("開始檢查服務器上有用更新....");
_downloader.SetProgressBar(_progress);
_downloader.Init();
_downloader.SetTrace(_log);
//加載Treeview
new FileView(_downloader.XmlServer, _progress).LoadTreeViewServer(_tvServerFiles);
new FileView(_downloader.XmlLocal, _progress).LoadTreeViewClient(_tvLocalFiles, _downloader.XmlServer);
if (_downloader.XmlLocal.HasNewVersion)
_log.Items.Add("服務器上有最新版本,請更新.");
else
_log.Items.Add("檢查完成,沒有發現新版本.");
}
public void BindControls(TreeView tvServerFiles, TreeView tvLocalFiles, ListBox log, ToolStripProgressBar progress)
{
_progress = progress;
_tvLocalFiles = tvLocalFiles;
_tvServerFiles = tvServerFiles;
_log = log;
}
}
文件對象定義
/// 文件對象
/// </summary>
public class FileInfo
{
private string _name = "";
private string _FullPath = "";
private DateTime _ModifyTime = DateTime.MinValue;
public FileInfo() { }
public FileInfo( string fileName, string fullPath, DateTime lastEditDate)
{
this.Name = fileName;
this.FullPath = fullPath;
this.ModifyTime = lastEditDate;
}
/// <summary>
/// 純文件名,不包含路徑。:如upgrader.exe
/// </summary>
public string Name { get { return _name; } set { _name = value; } }
/// <summary>
/// 文件完整路徑。如:[.\8.8.8.2\abc.dll]
/// </summary>
public string FullPath { get { return _FullPath; } set { _FullPath = value; } }
/// <summary>
/// 最后更新時間。
/// </summary>
public DateTime ModifyTime { get { return _ModifyTime; } set { _ModifyTime = value; } }
public override string ToString()
{
return this.Name;
}
}
XML文件解釋器,分析服務器/客戶端的xml文件
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Collections;
using System.Windows.Forms;
namespace CSFramework.Tech.AutoUpgraderLib
{
/// <summary>
/// XML文件解釋器,分析服務器/客戶端文件清單。
/// </summary>
public class XmlLoader
{
private XmlDocument _xml;
public XmlDocument XML { get { return _xml; } }
private string _xmlFilePath;
public XmlLoader( string xmlFile)
{
_xml = new XmlDocument();
_xmlFilePath = xmlFile;
if (System.IO.File.Exists(xmlFile)) _xml.Load(xmlFile);
}
private bool _HasNewVersion = false;
//本機文件清單與服務器文件清單比較后是否發現新版本。
public bool HasNewVersion { get { return _HasNewVersion; } set { _HasNewVersion = value; } }
/// <summary>
/// 創建空的Xml文件
/// </summary>
/// <param name="xmlFilePath"></param>
public static XmlLoader CreateEmpty( string xmlFilePath)
{
string xml =
"<?xml version=\"1.0\" encoding=\"utf-8\" ?> \r\n" +
"<Upgrader> \r\n" +
"<description>本機最近更新清單</description> \r\n" +
"<Application> \r\n" +
"<LastUpdateTime value=\"\" /> \r\n" +
"<Version value=\"\" /> \r\n" +
"</Application> \r\n" +
"<Files> \r\n" +
"</Files> \r\n" +
"</Upgrader> \r\n";
//刪除舊的更新文件
if (File.Exists(xmlFilePath)) File.Delete(xmlFilePath);
string dir = Path.GetDirectoryName(xmlFilePath);
if (!Directory.Exists(dir)) throw new Exception("不存在目錄:" + dir);
StreamWriter sw = File.CreateText(xmlFilePath);
sw.Write(xml);
sw.Flush();
sw.Close();
return new XmlLoader(xmlFilePath);
}
//保存最后更新信息
public void SetLastUpdateInfo( string version, DateTime lastUpdateTime)
{
XmlNode nodeVersion = _xml.SelectSingleNode("Upgrader/Application/Version");
XmlNode nodeTime = _xml.SelectSingleNode("Upgrader/Application/LastUpdateTime");
nodeVersion.Attributes["value"].Value = version;
nodeTime.Attributes["value"].Value = lastUpdateTime.ToString();
}
//獲取xml文件版本信息
public string GetVersion()
{
XmlNode ver = _xml.SelectSingleNode("Upgrader/Application/Version");
if (ver != null)
return ver.Attributes["value"].Value;
else
return "";
}
/// <summary>
/// 比較服務器與本機文件的最后更新時間。
/// 返回True:表示可以更新。False:服務器與本機文件版本一致。
/// </summary>
/// <param name="file">服務器上的文件</param>
/// <returns></returns>
public bool CompareFile(FileInfo file)
{
if (file == null) return true; //文件沒找到,為不相同,返回True;
XmlNode node = this.GetFileNode(file.FullPath);
if (node == null) return true; //文件沒找到,為不相同,返回True;
DateTime date;
if (DateTime.TryParse(node.Attributes["lastModify"].Value, out date))
{
return file.ModifyTime.CompareTo(date) > 0;
}
return false;
}
/// <summary>
/// 比較兩個XmlNode的日期大小
/// </summary>
public bool CompareNode(XmlNode node1, XmlNode node2)
{
if (node1 == null || node2 == null) return false;
DateTime date1 = Common.StrToDate(node1.Attributes["lastModify"].Value);
DateTime date2 = Common.StrToDate(node2.Attributes["lastModify"].Value);
return date1.CompareTo(date2) > 0;
}
//獲取在xml文件的結點
public XmlNode GetFileNode( string fullPath)
{
string xPath = @"Upgrader/Files/File[@fullPath=''" + fullPath + "'']";
XmlNode node = _xml.SelectSingleNode(xPath);
return node;
}
//獲取在xml文件的結點,轉換為FileInfo對象。
public FileInfo GetFileInfo( string fullPath)
{
XmlNode node = this.GetFileNode(fullPath);
if (node != null)
{
FileInfo fi = new FileInfo();
fi.FullPath = node.Attributes["fullPath"].Value;
fi.ModifyTime = Common.StrToDate(node.Attributes["lastModify"].Value);
fi.Name = node.Attributes["fileName"].Value;
return fi;
}
return null;
}
/// <summary>
/// 在客戶端的Xml記錄文件加入更新記錄
/// </summary>
/// <param name="FileInfo"></param>
/// <param name="serverFile"></param>
public void AddOrUpdateHistory(FileInfo serverFile, FileInfo clientFile)
{
XmlNode node = GetFileNode(serverFile.FullPath);
if (node == null)
{
XmlNode fileRoot = _xml.SelectSingleNode("Upgrader/Files");
node = _xml.CreateNode(XmlNodeType.Element, "File", "");
fileRoot.AppendChild(node);
}
node.RemoveAll(); //先刪除結點內的數據
node.Attributes.Append(CreateAttribute("fileName", clientFile.Name));
node.Attributes.Append(CreateAttribute("fullPath", serverFile.FullPath));
node.Attributes.Append(CreateAttribute("lastModify", serverFile.ModifyTime.ToString()));
}
private XmlAttribute CreateAttribute( string name, string value)
{
XmlAttribute attr = _xml.CreateAttribute(name);
attr.Value = value;
return attr;
}
//保存歷史記錄
public void Save()
{
_xml.Save(_xmlFilePath);
}
/// <summary>
/// 獲取文件數量
/// </summary>
public int FilesCount
{
get
{
XmlNode node = _xml.SelectSingleNode("Upgrader/Files");
return node.ChildNodes.Count;
}
}
/// <summary>
/// 獲取文件列表
/// </summary>
public IList GetFiles()
{
IList files = new ArrayList();
XmlNode node = _xml.SelectSingleNode("Upgrader/Files");
foreach (XmlNode n in node.ChildNodes)
{
FileInfo sf = new FileInfo();
sf.FullPath = n.Attributes["fullPath"].Value;
sf.ModifyTime = Common.StrToDate(n.Attributes["lastModify"].Value);
sf.Name = n.Attributes["fileName"].Value;
files.Add(sf);
}
return files;
}
}
}