[置顶] 【CS】客户端更新(一)——更新程序文件方式
2017-04-21 17:04
387 查看
一、前言
最近接手了个半CS半BS的项目。怎么说呢?由于项目比较紧张,而且BS的项目已经做出来了,虽说不是很好,但是也可以满足增删改查的操作。但是CS的项目比较紧,给了一个月的时间,如果每个功能都做的话,时间根本不够,就算时间够,资金也不够。所以就在CS的界面中调用了BS的界面,然后界面显示的是BS的信息。但是CS存在一个问题啊!那就是更新啊?CS的软件肯定有更新的功能,所以在以后的更新过程中一定会有变化的。在这篇博客中,小编就说说软件更新。
二、说说更新
提到更新,最常见的无非分为两种:更新改变的文件
下载最新的安装包,重新安装,但是要保留用户的相关信息
小编在这篇博客中着重介绍一下第一种,把改变的文件更新到服务器,,然后客户端运行后会自动检查是否存在更新,存在更新就把文件下载下来,同名的文件会被新的文件覆盖。
三、更新程序文件
3.1 思路图
日行千里,先找对方向。解析:
在图中,分成了两个部分:服务器+客户端。服务器主要是用于存放系统更新的文件以及更新的xml文件。而客户端就是我们使用的程序,类似QQ。
当我们的服务器配置文件更新后,客户端检测到后,就会提示更新,显示更新的内容,然后开始下载内容,最后同步服务器和客户端的配置文件。确保是同一个版本。
3.2 更新环境搭建
3.2.1 程序搭建
对于更新的程序小编是把它取出来,作为一个独立的程序,当主程序运行的时候会检测是否存在更新。来调用更新程序编译好的exe文件。3.2.2 服务器搭建
服务器的选择可以是iis,ftp,weblogic,tomcat等。小编这里选择的是iis和ftp,其他的服务器会在以后展示。具体搭建请参考:[align=center]【BS】Windwos server 2008 服务器安装 IIS[/align]
[align=center]【B/S】IIS的配置以及发布网站[/align]
[align=center] C# 之 FTP服务器中文件上传与下载(一)[/align]
[align=center]解决IIS 不能下载.MP4.dat .lib .pdb .ini后缀文件的方法[/align]
3.3 检查更新,检查是否存在更新
判断条件:通过对比本地的xml文件中的总版本信息和服务器端的总版本信息是否相同。不相同则是存在更新,相同这是没有更新。通过调用app.IsUpdate方法来判断
#region 检查是否存在更新-王雷-2017年4月13日16:58:50 /// <summary> /// 检查是否存在更新-王雷-2017年4月13日16:58:50 /// </summary> public static void checkUpdate() { //获得程序的exe文件路径 SoftUpdate app = new SoftUpdate(Application.ExecutablePath, "BlogWriter"); app.UpdateFinish += new UpdateState(app_UpdateFinish); try { //判断是否要更新 if (app.IsUpdate && MessageBox.Show("检查到新版本,是否更新?", "Update", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { //如果要更新就打开更新的页面 FrmUpdate fUpdate = new FrmUpdate(); fUpdate.ShowDialog(); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } #endregion
在IsUpdate方法中会调用checkUpdate方法来检查:
#region 获取是否需要更新-王雷-2017年4月13日17:01:37 /// <summary> /// 获取是否需要更新 /// </summary> public bool IsUpdate { get { checkUpdate(); return isUpdate; } } #endregion
在checkUpdate方法中,主要是通过对比本地的xml文件中的总版本信息和服务器端的总版本信息是否相同。不相同则是存在更新
#region 检查是否需要更新-比较本地的xml文件中的总版本信息和服务器端的总版本信息-王雷-2017年4月13日17:04:05 /// <summary> /// 检查是否需要更新-比较本地的xml文件中的总版本信息和服务器端的总版本信息-王雷-2017年4月13日17:04:05 /// </summary> public void checkUpdate() { try { //从本地的xml文件中提取出服务器的链接 string xmlLocal = Application.StartupPath + @"\UpdateList.xml"; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(xmlLocal); XmlNode list = xmlDoc.SelectSingleNode("//Updater"); foreach (XmlNode node in list) { if (node.Name == "Url") { UrlServer = node.InnerText; } } UrlServer = UrlServer + "/UpdateList.xml"; //获取服务端的版本号 string verServer = getVersion(UrlServer); //获取本地的版本号 string verLocal = getVersion(xmlLocal); //比较版本号 if (verServer != verLocal) { isUpdate = true; //需要更新 } else { isUpdate = false; } } catch (Exception ex) { throw new Exception("更新出现错误,请确认网络连接无误后重试!"); } } #endregion
在文件中存在根据xml文件的路径获取版本号Version节点下的值,这涉及到了读xml文件的知识。http://www.jb51.net/article/56289.htm博客可以介绍一下。对xml文件的增删改查。
#region 根据xml文件的路径获取版本号Version节点下的值-王雷-2017年4月13日17:05:05 /// <summary> /// 根据xml文件的路径获取版本号Version节点下的值-王雷-2017年4月13日17:05:05 /// </summary> /// <param name="URL">xml文件的路径</param> /// <returns>string</returns> public string getVersion(string URL) { WebClient wc = new WebClient(); Stream stream = wc.OpenRead(URL); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(stream); XmlNode list = xmlDoc.SelectSingleNode("//Update"); foreach (XmlNode node in list) { if (node.Name == "Soft" && node.Attributes["Name"].Value.ToLower() == SoftName.ToLower()) { foreach (XmlNode xml in node) { if (xml.Name == "Verson") newVerson = xml.InnerText; else download = xml.InnerText; } } } return newVerson; } #endregion
如果存在更新就会弹框显示:
3.4 显示手动更新页面
手动更新加载的页面流程:1.从本地的配置文件读取出服务器的连接。
2.拼接出服务器上的配置文件的路径,获取服务器地址
3.与服务器连接,把服务器上的xml文件下载到建立的临时文件中。C:\Users\Ares\AppData\Local\Temp_ItemSoft_y_x_m_\
4.检查更新文件
#region 界面加载-检查出要更新的文件-王雷-2017年4月13日17:10:28 /// <summary> /// 界面加载-检查出要更新的文件-王雷-2017年4月13日17:10:28 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void FrmUpdate_Load(object sender, System.EventArgs e) { panel2.Visible = false; btnFinish.Visible = false; //1.获取本地xml文件的路径 string localXmlFile = Application.StartupPath + "\\UpdateList.xml"; string serverXmlFile = string.Empty; try { //从本地读取更新配置文件信息 updaterXmlFiles = new XmlFiles(localXmlFile); } catch { MessageBox.Show("配置文件出错!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); this.Close(); return; } //2.获取服务器地址 updateUrl = updaterXmlFiles.GetNodeValue("//Url"); AppUpdater appUpdater = new AppUpdater(); appUpdater.UpdaterUrl = updateUrl + "/UpdateList.xml"; //3.与服务器连接,下载更新配置文件 try { tempUpdatePath = Environment.GetEnvironmentVariable("Temp") + "\\" + "_" + updaterXmlFiles.FindNode("//Application").Attributes["applicationId"].Value + "_" + "y" + "_" + "x" + "_" + "m" + "_" + "\\"; //删除临时目录中的所有文件 DelectDir(tempUpdatePath); //下载更新文件的临时目录 appUpdater.DownAutoUpdateFile(tempUpdatePath); } catch { MessageBox.Show("与服务器连接失败,操作超时!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); this.Close(); return; } //获取更新文件列表 Hashtable htUpdateFile = new Hashtable(); //拼接临时存放文件夹的路径 serverXmlFile = tempUpdatePath + "\\UpdateList.xml"; if (!File.Exists(serverXmlFile)) { return; } //检查更新文件 availableUpdate = appUpdater.CheckForUpdate(serverXmlFile, localXmlFile, out htUpdateFile); if (availableUpdate > 0) { for (int i = 0; i < htUpdateFile.Count; i++) { string[] fileArray = (string[])htUpdateFile[i]; lvUpdateList.Items.Add(new ListViewItem(fileArray)); } } } #endregion
如果更新失败,就会在临时文件中存储已经下载的内容,对下次的尝试造成不便,所以对系统进行临时文件删除:
#region 删除因为错误而产生的临时文件-王雷-2017年4月21日10:24:34 /// <summary> /// 删除因为错误而产生的临时文件-王雷-2017年4月21日10:24:34 /// </summary> /// <param name="srcPath">临时文件目录</param> public static void DelectDir(string srcPath) { try { DirectoryInfo dir = new DirectoryInfo(srcPath); bool flag = dir.Exists; if (flag) { FileSystemInfo[] fileinfo = dir.GetFileSystemInfos(); //返回目录中所有文件和子目录 foreach (FileSystemInfo i in fileinfo) { if (i is DirectoryInfo) //判断是否文件夹 { DirectoryInfo subdir = new DirectoryInfo(i.FullName); subdir.Delete(true); //删除子目录和文件 } else { File.Delete(i.FullName); //删除指定文件 } } } } catch (Exception e) { throw; } } #endregion
把服务器的xml文件下载到临时文件,临时文件的路径是C:\Users\Ares\AppData\Local\Temp_ItemSoft_y_x_m_\
#region 返回下载更新文件的临时目录-王雷-2017年4月13日17:11:06 /// <summary> /// 返回下载更新文件的临时目录-王雷-2017年4月13日17:11:06 /// </summary> /// <returns></returns> public void DownAutoUpdateFile(string downpath) { if (!System.IO.Directory.Exists(downpath)) System.IO.Directory.CreateDirectory(downpath); string serverXmlFile = downpath + @"/UpdateList.xml"; try { WebRequest req = WebRequest.Create(this.UpdaterUrl); WebResponse res = req.GetResponse(); if (res.ContentLength > 0) { try { WebClient wClient = new WebClient(); wClient.DownloadFile(this.UpdaterUrl, serverXmlFile); } catch { return; } } } catch { return; } //return tempPath; } #endregion
产生的临时文件目录,会把要更新的文件先下载到临时的文件中,起中转站的作用。
3.5 检查更新文件
通过对比从服务器上下载的xml文件和本地软件的xml软件来获得由多少条更新的记录1.加载xml文件
2.把AutoUpdater/Files下的所有的子节点都存储在list中
3.遍历
4.取出newNodeList中节点名为Name,和Ver的值,和oldNodeList中的各个节点比较,如果两个都相同,则不用更新这条记录,否则需要更新。并把这条要更新的记录添加到updateFileList中。最后依次遍历updateFileList中的值,把信息显示到界面上。
#region 检查更新文件-王雷-2017年4月13日17:12:03 /// <summary> /// 检查更新文件-王雷-2017年4月13日17:12:03 /// </summary> /// <param name="serverXmlFile">服务器端xml文件的路径</param> /// <param name="localXmlFile">本地xml文件的路径</param> /// <param name="updateFileList">要更新文件的列表</param> /// <returns></returns> public int CheckForUpdate(string serverXmlFile, string localXmlFile, out Hashtable updateFileList) { updateFileList = new Hashtable(); if (!File.Exists(localXmlFile) || !File.Exists(serverXmlFile)) { return -1; } //加载xml文件 XmlFiles serverXmlFiles = new XmlFiles(serverXmlFile); XmlFiles localXmlFiles = new XmlFiles(localXmlFile); //把AutoUpdater/Files下的所有的子节点都存储在list中 XmlNodeList newNodeList = serverXmlFiles.GetNodeList("AutoUpdater/Files"); XmlNodeList oldNodeList = localXmlFiles.GetNodeList("AutoUpdater/Files"); int k = 0; for (int i = 0; i < newNodeList.Count; i++) { string[] fileList = new string[3]; string newFileName = newNodeList.Item(i).Attributes["Name"].Value.Trim(); string newVer = newNodeList.Item(i).Attributes["Ver"].Value.Trim(); ArrayList oldFileAl = new ArrayList(); for (int j = 0; j < oldNodeList.Count; j++) { string oldFileName = oldNodeList.Item(j).Attributes["Name"].Value.Trim(); string oldVer = oldNodeList.Item(j).Attributes["Ver"].Value.Trim(); oldFileAl.Add(oldFileName); oldFileAl.Add(oldVer); } int pos = oldFileAl.IndexOf(newFileName); if (pos == -1) { fileList[0] = newFileName; fileList[1] = newVer; updateFileList.Add(k, fileList); k++; } else if (pos > -1 && newVer !=oldFileAl[pos + 1].ToString()) { fileList[0] = newFileName; fileList[1] = newVer; updateFileList.Add(k, fileList); k++; } } return k; } #endregion
界面显示:
3.6 点击下一步,下载文件
下载效果:在这里使用了BackgroundWorker组件,以及通过委托进行下载文件。
BackgroundWorker 组件用来执行诸如数据库事务、文件下载等耗时的异步操作。
#region 点击下一步-开始下载要更新的文件-存在的覆盖-王雷-2017年4月13日17:15:04 /// <summary> /// 点击下一步-开始下载要更新的文件-存在的覆盖-王雷-2017年4月13日17:15:04 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnNext_Click(object sender, System.EventArgs e) { if (availableUpdate > 0) { using (BackgroundWorker bw = new BackgroundWorker()) { bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); bw.DoWork += new DoWorkEventHandler(DownUpdateFile); bw.RunWorkerAsync(); } } //if (availableUpdate > 0) //{ // Thread threadDown=new Thread(new ThreadStart(DownUpdateFile)); // threadDown.IsBackground = true; // threadDown.Start(); //} else { MessageBox.Show("没有可用的更新!", "自动更新", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } } #endregion #region 委托方法-线程完成结束操作-王雷-2017年4月13日17:16:42 /// <summary> /// 委托方法-线程完成结束操作-王雷-2017年4月13日17:16:42 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //这时后台线程已经完成,并返回了主线程,所以可以直接使用UI控件了 this.Cursor = Cursors.Default; } #endregion #region 下载文件-王雷-2017年4月13日17:15:52 /// <summary> /// 下载文件-王雷-2017年4月13日17:15:52 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void DownUpdateFile(object sender, DoWorkEventArgs e) { //C#跨线程访问控件。 //http://www.cnblogs.com/TankXiao/p/3348292.html //this.Cursor = Cursors.WaitCursor; mainAppExe = updaterXmlFiles.GetNodeValue("//EntryPoint"); Process[] allProcess = Process.GetProcesses(); foreach (Process p in allProcess) { if (p.ProcessName.ToLower() + ".exe" == mainAppExe.ToLower()) { for (int i = 0; i < p.Threads.Count; i++) p.Threads[i].Dispose(); p.Kill(); isRun = true; //break; } } WebClient wcClient = new WebClient(); for (int i = 0; i < this.lvUpdateList.Items.Count; i++) { string UpdateFile = lvUpdateList.Items[i].Text.Trim(); string updateFileUrl = updateUrl + lvUpdateList.Items[i].Text.Trim(); long fileLength = 0; try { WebRequest webReq = WebRequest.Create(updateFileUrl); WebResponse webRes = webReq.GetResponse(); fileLength = webRes.ContentLength; //fileLength = 100; lbState.Text = "正在下载更新文件,请稍后..."; pbDownFile.Value = 0; pbDownFile.Maximum = (int)fileLength; Stream srm = webRes.GetResponseStream(); //StreamReader srmReader = new StreamReader(srm); byte[] bufferbyte = new byte[fileLength]; int allByte = (int)bufferbyte.Length; int startByte = 0; while (fileLength > 0) { Application.DoEvents(); int downByte = srm.Read(bufferbyte, startByte, allByte); if (downByte == 0) { break; }; startByte += downByte; allByte -= downByte; pbDownFile.Value += downByte; float part = (float)startByte / 1024; float total = (float)bufferbyte.Length / 1024; int percent = Convert.ToInt32((part / total) * 100); this.lvUpdateList.Items[i].SubItems[2].Text = percent.ToString() + "%"; } UpdateFile = UpdateFile.Replace("/", "\\"); string tempPath = tempUpdatePath + UpdateFile; CreateDirtory(tempPath); FileStream fs = new FileStream(tempPath, FileMode.OpenOrCreate, FileAccess.Write); fs.Write(bufferbyte, 0, bufferbyte.Length); srm.Close(); //srmReader.Close(); fs.Close(); } catch (WebException ex) { if (ex.Message.ToString()=="远程服务器返回错误: (404) 未找到。") { MessageBox.Show(UpdateFile+"更新文件下载失败!" , "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } else { MessageBox.Show("更新文件下载失败!" + ex.Message.ToString(), "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } InvalidateControl(); this.Cursor = Cursors.Hand; } #endregion #region 创建目录-王雷-2017年4月13日17:17:13 //创建目录 private void CreateDirtory(string path) { if (!File.Exists(path)) { string[] dirArray = path.Split('\\'); string temp = string.Empty; for (int i = 0; i < dirArray.Length - 1; i++) { temp += dirArray[i].Trim() + "\\"; if (!Directory.Exists(temp)) Directory.CreateDirectory(temp); } } } #endregion
3.6 下载完成,同步配置文件
完成效果:最快的同步方法就是把服务器的文件复制到本地。
在这里要说明一下:如果我们要更新的是当前正在运行的进程,比如小编的是DESDecder.exe,那么我去更新它就会报“DESDecder.exe正在被另一个进程使用”的错误。所以我们要先把这个进程杀死,然后再去做更新的操纵。代码如下:
#region 点击完成复制更新文件到应用程序目录-王雷-2017年4月13日17:18:46 //点击完成复制更新文件到应用程序目录 private void btnFinish_Click(object sender, System.EventArgs e) { this.Close(); this.Dispose(); Process[] process = Process.GetProcesses(); foreach (Process prc in process) { if (prc.ProcessName == "DESDecder") { Thread t = new Thread(WriteY); t.Start(); prc.Kill(); } } try { CopyFile(tempUpdatePath, Directory.GetCurrentDirectory()); System.IO.Directory.Delete(tempUpdatePath, true); } catch (Exception ex) { MessageBox.Show(ex.Message.ToString()); } if (true == this.isRun) Process.Start(mainAppExe); } #endregion
复制文件:
#region 复制文件-王雷-2017年4月13日17:17:32 //复制文件; public void CopyFile(string sourcePath, string objPath) { if (!Directory.Exists(objPath)) { Directory.CreateDirectory(objPath); } string[] files = Directory.GetFiles(sourcePath); for (int i = 0; i < files.Length; i++) { string[] childfile = files[i].Split('\\'); File.Copy(files[i], objPath + @"\" + childfile[childfile.Length - 1], true); } string[] dirs = Directory.GetDirectories(sourcePath); for (int i = 0; i < dirs.Length; i++) { string[] childdir = dirs[i].Split('\\'); CopyFile(dirs[i], objPath + @"\" + childdir[childdir.Length - 1]); } } #endregion
四、小结
通过这次的实践自己也是通过借鉴分析,对比来获得的,然后把代码一点一点的分析出来,写出来的。其中也借鉴了很多其他博主的博客。非常感谢他们,代码虽多,但是功能可以实现,总是软件更新这个方面的东西还是我们要深入学习的。加油!福利:附软件开发示例源码。
相关文章推荐
- [置顶] 【CS】客户端更新(二)——生成更新配置文件程序介绍
- document.write 方式引入外部 JS 文件导致脚本程序执行顺序不同以及 DOM 树更新延迟问题
- 客户端程序自动更新(升级)的方式
- 以程序的方式操纵NTFS的文件权限(下)
- (原創) 如何用程序的方式载入indexd过的图形文件? (.NET) (ASP.NET) (C#) (GDI+) (Image Processing)
- PB使用WININET的FTP方式自动更新(三、获得要更新的文件数)
- 让客户端把ASP文件以XML的方式来处理最简单的实现方法.
- 以程序的方式操纵NTFS的文件权限(下)
- asp.net2.0 中最快方式实现gridview 更新 删除 xml文件
- (原創) 如何用程序的方式载入jpg图形文件? (.NET) (GDI+) (ASP.NET) (Image Processing)
- 以程序的方式操纵NTFS的文件权限(上)
- 以文本方式上传二进制文件的PHP程序
- Access数据库:它已经被别的用户以独占方式打开,操作必须使用一个可更新的查询,不能锁定文件
- 以程序的方式操纵NTFS的文件权限
- 以程序的方式操纵NTFS的文件权限
- 以文本方式上传二进制文件的PHP程序
- 以程序的方式操纵NTFS的文件权限(中)
- 以程序的方式操纵NTFS的文件权限(中)
- WEB方式下客户端无法上传文件
- 以程序的方式操纵NTFS的文件权限