一、前言

最近接手了个半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,其他的服务器会在以后展示。具体搭建请参考:

【BS】Windwos server 2008 服务器安装 IIS
【B/S】IIS的配置以及发布网站
C# 之 FTP服务器中文件上传与下载(一)
解决IIS 不能下载.MP4.dat .lib .pdb .ini后缀文件的方法

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;elsedownload = 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】客户端更新(一)——更新程序文件方式相关推荐

  1. linuxos或sv独立客户端不支持应用程序打开方式_搞不明白为什么大家都在学习 k8s

    作者 | 小明菜市场 来源 | 小明菜市场(ID:fileGeek) 头图 | CSDN 下载自东方IC 前言 都2020年了,你还不知道kubernetes就真的真的真的out啦.(贩卖焦虑体) 什 ...

  2. 客户端程序自动更新(升级)的方式

    from:https://blog.csdn.net/woaitingting1985/article/details/72954652 一.C/S自动更新原理 C/S程序自动升级是一个很重要的功能, ...

  3. c#实现客户端程序自动下载更新(单独程序)

    首先,自己工作需要实现客户端程序的自动更新下载,下面简单介绍自己实现逻辑和遇到的一些问题及解决方法 信息基本都是通过XML文件配置的,下文本地需要更新的程序简称为主程序 实现步骤简介: 1.获取本地程 ...

  4. 封印者无法从更新服务器获取补丁文件,封印者客户端打不开怎么办 封印者客户端打不开解决办法一览...

    封印者客户端打不开怎么办?封印者客户端打不开解决办法一览.封印者打不开怎么处理?很多朋友进入游戏的时候会遇到各种弹窗或者进不了游戏,碰到这种情况要怎么解决呢?下面就来看一看封印者打不开解决方法汇总,希 ...

  5. 二进制文件更新程序_APR 6.17程序文件更新

    兰博基尼程序文件更新 Lamborghini Huracan EURO MY2018 5.2L V10 DKBC 4T0907552L S0002 Stage 1 V1.1 [APR Mobile] ...

  6. Netbackup8.0以上版本,服务端生成证书,客户端获取、更新证书方式(整理中)

    创建重发令牌 如果非主控主机已在主服务器上注册但其基于主机ID的证书不再有效,则可以重新颁发基于主机ID的证书.例如,证书在过期,被撤销或丢失时无效. 重发令牌是一种可用于重新颁发证书的令牌.它是一种 ...

  7. centos 时间同步_ftp同步更新,ftp远程文件同步更新日志详情

    FTP远程文件同步更新程序当你需要频繁的将更新的文件向远程电脑传送时,普通的FTP客户端软件的操作则显得费时费力,使用文件夹共享安全性又不高,使用本程序则能轻松的解决这个问题,本程序可以定时自动检测本 ...

  8. python flask热更新_客户端python热更新

    介绍: 热更新,就是在维持服务不间断的情况下,对软件代码逻辑或配置数据进行更新修复.随着游戏项目引入了脚本语言以后,热更新技术逐渐成为了标配,在我经历过的游戏项目中,无论是服务端还是客户端,版本的更新 ...

  9. 计算机缺少更新,电脑更新系统出现文件丢失或者损坏无法安装怎么办 | 学客联盟...

    电脑在升级Windows 10系统的时候,系统意外出现"该提示意味着Windows 10更新所需的文件丢失或者损坏"的问题,怎么样解决问题. ​安装过程中遇到存储空间不足的问题怎么 ...

最新文章

  1. 图像处理(二)Seam Carving算法-Siggraph 2007
  2. 四核处理器_(技术文档)你知道AMD Ryzen处理器中的CCX与CCD是什么吗?
  3. [链表] --- 反转链表(leetcode 206)
  4. 【洛谷习题】填涂颜色
  5. 信息学奥赛一本通C++语言——1054:三角形判断
  6. Allegro 导入ASC file的步骤
  7. Xp下的程序编译成linux,Windows XP下硬盘安装Linux系统
  8. 论软件定义GPU对AI数据中心优化的必要性
  9. zookeeper3.4.6完全分布式安装
  10. 「数字电子技术基础」6.触发器
  11. MySQL系列:表空间加密
  12. python讲师陈越_浙大陈越老师数据结构课件
  13. [毕业生的商业软件开发之路]积累与创新
  14. 珍藏书籍,人工智能书籍推荐--AI“圣经”/超详细计算机视觉书籍赠送
  15. NANK南卡降噪耳机和OPPO蓝牙耳机哪个更好呢?哪款更能打?
  16. 移动网页支付(微信H5支付和支付宝网页支付)
  17. (3)登录界面——登录
  18. 《每日论文》ImageNet Classification with Deep Convolutional Neural Networks
  19. Spring boot JPA+Gradle+QueryDSL 完美配置生成Q文件依赖
  20. 现代数字信号处理——AR模型

热门文章

  1. 2.3 Openwrt 模拟 spi 及其sx1301寄存器读写测试
  2. docsify学习笔记
  3. 组合数学 | 排列与组合
  4. CorelDRAW利用图纸工具制作表格(一)
  5. GfK数据:尽管销量下滑 全球智能手机的营收仍在增长
  6. sm5401锂电池充放电管理IC
  7. Pix4飞控常见问题解决方法(二)
  8. 网络拓扑 代码 matlab,matlabdaima 复杂网络拓扑特征统计的编程代码,可以计算 中各种 值 Other systems 其他 246万源代码下载- www.pudn.com...
  9. 计算机创新大赛参赛表,计算机科学学院 “互联网+”大学生创新创业大赛师生参赛奖励办法...
  10. 罗马时钟代码 jquery