当前位置:   article > 正文

C# 自动更新(基于FTP)_c# 程序自动升级

c# 程序自动升级

目录

一、前言

二、功能的实现

1.本地黑名单

2.读取配置文件

3.读取 FTP 文件列表

4.读取本地文件

5.匹配更新

6.版本的切换

三、环境搭建

四、常见问题

2023.12.30 更新

结束


效果

启动软件后,会自动读取所有的 FTP 服务器文件,然后读取本地需要更新的目录,进行匹配,将 FTP 服务器的文件同步到本地

Winform 界面

一、前言

在去年,我写了一个 C# 版本的自动更新,这个是根据配置文件 + 网站文件等组成的框架,以实现本地文件的新增、替换和删除,虽然实现了自动更新的功能,但用起来过于复杂,代码量也比较大,改起来困难,后面我就想能不能弄一个 FTP 服务器进行版本的更新。平时客户端版本的更新,一般就两个需求,1.将服务器端最新的文件同步到本地,2.版本回退,如果当前版本有bug,可以随意的切换想要的版本号,这个功能在 FTP 服务器实现起来也比较简单,在 FTP 服务器里新建一个对应版本的文件夹,把对应版本的文件放进去就好了,想切换那个版本,就把 FTP 链接地址指向这个文件夹,然后同步到本地就好了,知道了这个原理,那么就来实现吧。

二、功能的实现

新建一个 winform 项目,界面如下

这几个控件分别是文件名,文件下载的进度,下载进度的百分比,具体控件名可以在源码中查看

form1 代码

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Security.Cryptography;
  6. using System.Threading.Tasks;
  7. using System.Windows.Forms;
  8. namespace update
  9. {
  10. public partial class Form1 : Form
  11. {
  12. public Form1()
  13. {
  14. InitializeComponent();
  15. }
  16. #region 字段
  17. /// <summary>
  18. /// 需要和FTP服务器对比的本地路径
  19. /// </summary>
  20. private string TargetPath = string.Empty;
  21. /// <summary>
  22. /// FTP文件夹列表
  23. /// </summary>
  24. private List<string> FTPDirectoryList = new List<string>();
  25. /// <summary>
  26. /// FTP文件列表
  27. /// </summary>
  28. private List<FileInfo> FTPFileList = new List<FileInfo>();
  29. /// <summary>
  30. /// 本地文件夹列表
  31. /// </summary>
  32. private List<string> LocalDirectorysList = new List<string>();
  33. /// <summary>
  34. /// 本地文件列表
  35. /// </summary>
  36. private List<FileInfo> LocalFilesList = new List<FileInfo>();
  37. /// <summary>
  38. /// 本地文件的黑名单(不参与到更新)
  39. /// </summary>
  40. private List<string> LocalFileBlacklist = new List<string>();
  41. /// <summary>
  42. /// ftp 和本地匹配结果,需要处理的数据
  43. /// </summary>
  44. private UpdateResultInfo UpdateResultData = null;
  45. //读取本地文件完成
  46. private bool ReadLocalEnd = false;
  47. //读取ftp文件完成
  48. private bool ReadFTPEnd = false;
  49. #endregion
  50. private void Form1_Load(object sender, EventArgs e)
  51. {
  52. TargetPath = Application.StartupPath;
  53. FTPManager.DownloadProgressAction = DownProgressUpdate;
  54. //添加黑名单
  55. AddBlacklist();
  56. //读取配置文件
  57. ReadConfiguration();
  58. Start();
  59. }
  60. private async void Start()
  61. {
  62. //刚启动就读取,会导致界面无法显示
  63. await Task.Delay(500);
  64. //读取 FTP 所有的文件
  65. ReadFTPFile();
  66. //读取本地文件
  67. ReadLocalFile();
  68. }
  69. /// <summary>
  70. /// 添加黑名单
  71. /// </summary>
  72. private void AddBlacklist()
  73. {
  74. LocalFileBlacklist.Add("update.exe");
  75. LocalFileBlacklist.Add("update.exe.config");
  76. LocalFileBlacklist.Add("update.pdb");
  77. }
  78. /// <summary>
  79. /// 显示下载进度
  80. /// </summary>
  81. /// <param name="fileName"></param>
  82. /// <param name="totalBytes"></param>
  83. /// <param name="totalDownloadBytes"></param>
  84. /// <param name="percent"></param>
  85. public void DownProgressUpdate(string fileName, double totalBytes, double totalDownloadBytes, int percent)
  86. {
  87. //Console.WriteLine("文件名:{0},总进度:{1},下载进度:{2},百分比:{3}", fileName, totalBytes, totalDownloadBytes, percent);
  88. FormControlExtensions.InvokeIfRequired(this, () =>
  89. {
  90. Label_FileName.Text = fileName;
  91. Label_Speed.Text = string.Format("{0} / {1}", GetSize(totalBytes), GetSize(totalDownloadBytes));
  92. Label_Percentage.Text = string.Format("{0}%", percent);
  93. ProgressBar_DownProgress.Value = percent;
  94. });
  95. }
  96. /// <summary>
  97. /// 读取 FTP 所有的文件
  98. /// </summary>
  99. private void ReadFTPFile()
  100. {
  101. FTPDirectoryList.Clear();
  102. FTPFileList.Clear();
  103. Console.WriteLine("开始读取 FTP 文件");
  104. Task.Run(() =>
  105. {
  106. Tuple<List<string>, List<FileInfo>> tuple = FTPManager.GetAllFileList();
  107. FTPDirectoryList = tuple.Item1;
  108. FTPFileList = tuple.Item2;
  109. ReadLocalEnd = true;
  110. Console.WriteLine("读取FTP所有的文件完成");
  111. ReadEnd();
  112. });
  113. }
  114. /// <summary>
  115. /// 读取本地文件
  116. /// </summary>
  117. private void ReadLocalFile()
  118. {
  119. LocalDirectorysList.Clear();
  120. LocalFilesList.Clear();
  121. Console.WriteLine("开始读取本地文件");
  122. GetDirectoryFileList(TargetPath);
  123. ReadFTPEnd = true;
  124. Console.WriteLine("读取本地文件完成");
  125. ReadEnd();
  126. }
  127. /// <summary>
  128. /// 获取一个文件夹下的所有文件和文件夹
  129. /// </summary>
  130. /// <param name="path"></param>
  131. private void GetDirectoryFileList(string path)
  132. {
  133. DirectoryInfo directory = new DirectoryInfo(path);
  134. FileSystemInfo[] filesArray = directory.GetFileSystemInfos();
  135. if (filesArray.Length == 0) return;
  136. foreach (var item in filesArray)
  137. {
  138. if (item.Attributes == FileAttributes.Directory)
  139. {
  140. //添加文件夹
  141. //string dir = item.FullName.Replace(path, "");
  142. LocalDirectorysList.Add(item.FullName);
  143. GetDirectoryFileList(item.FullName);
  144. }
  145. else
  146. {
  147. //文件名
  148. string fileName = Path.GetFileName(item.FullName);
  149. //是否在黑名单中
  150. if (!LocalFileBlacklist.Any(p => p == fileName))
  151. {
  152. FileInfo fileType = new FileInfo();
  153. fileType.FileName = fileName;
  154. //fileType.LastModified = File.GetLastWriteTime(item.FullName);
  155. //fileType.FileSize = new System.IO.FileInfo(item.FullName).Length;
  156. fileType.Path = item.FullName;
  157. fileType.Hash = GetHashs(item.FullName);
  158. LocalFilesList.Add(fileType);
  159. }
  160. }
  161. }
  162. }
  163. /// <summary>
  164. /// 读取配置文件
  165. /// </summary>
  166. private void ReadConfiguration()
  167. {
  168. string ftpUrl = ConfigHelper.GetAppConfig("FtpUrl");
  169. string ftpUser = ConfigHelper.GetAppConfig("FtpUser");
  170. string ftpPassword = ConfigHelper.GetAppConfig("FtpPassword");
  171. if(string.IsNullOrEmpty(ftpUrl) )
  172. {
  173. Console.WriteLine("FTP IP地址为空");
  174. return;
  175. }
  176. if(string.IsNullOrEmpty(ftpUser) )
  177. {
  178. Console.WriteLine("FTP 用户名地址为空");
  179. return;
  180. }
  181. if(string.IsNullOrEmpty(ftpPassword) )
  182. {
  183. Console.WriteLine("FTP 用户密码地址为空");
  184. return;
  185. }
  186. FTPManager.ftpUrl = ftpUrl;
  187. FTPManager.user = ftpUser;
  188. FTPManager.password = ftpPassword;
  189. Console.WriteLine("读取配置文件完成");
  190. }
  191. /// <summary>
  192. /// 获取字节大小
  193. /// </summary>
  194. /// <param name="size"></param>
  195. /// <returns></returns>
  196. private string GetSize(double size)
  197. {
  198. String[] units = new String[] { "B", "KB", "MB", "GB", "TB", "PB" };
  199. double mod = 1024.0;
  200. int i = 0;
  201. while (size >= mod)
  202. {
  203. size /= mod;
  204. i++;
  205. }
  206. return Math.Round(size) + units[i];
  207. }
  208. /// <summary>
  209. /// 获取文件的哈希值
  210. /// </summary>
  211. /// <param name="path"></param>
  212. /// <returns></returns>
  213. private string GetHashs(string path)
  214. {
  215. //创建一个哈希算法对象
  216. using (HashAlgorithm hash = HashAlgorithm.Create())
  217. {
  218. using (FileStream file1 = new FileStream(path, FileMode.Open))
  219. {
  220. //哈希算法根据文本得到哈希码的字节数组
  221. byte[] hashByte1 = hash.ComputeHash(file1);
  222. //将字节数组装换为字符串
  223. return BitConverter.ToString(hashByte1);
  224. }
  225. }
  226. }
  227. /// <summary>
  228. /// 所有的文件读取完成后
  229. /// </summary>
  230. private void ReadEnd()
  231. {
  232. if (!ReadLocalEnd || !ReadFTPEnd)
  233. return;
  234. Console.WriteLine("所有的文件读取完成");
  235. FormControlExtensions.InvokeIfRequired(this, () => ProgressBar_DownProgress.Visible = true );
  236. Task.Run(() =>
  237. {
  238. UpdateResultData = UpdateMatching.DetectUpdates(FTPDirectoryList, FTPFileList, LocalDirectorysList, LocalFilesList, FTPManager.ftpUrl, TargetPath);
  239. UpdateMatching.StartUpdate(UpdateResultData);
  240. Console.WriteLine("所有文件更新完成");
  241. //FormControlExtensions.InvokeIfRequired(this, () => ProgressBar_DownProgress.Visible = true);
  242. });
  243. }
  244. }
  245. }

软件在启动后,就会自动进行文件匹配,判断那些文件是否需要更新,但在做之前,需要先做几件事

1.本地黑名单

本地的有些文件是不必参与到更新的,比如将 update.exe 这个文件放在更新目录的,而且当前已经打开,总不能自己删除自己吧,所有有关 update.exe 相关的文件都不能参与到更新中,另一个,其他一些不需要参与到更新的文件都可以添加到黑名单中。

2.读取配置文件

ftp 的链接地址,用户名和密码,这些都是不能在代码中写死的,我一般写在配置文件中,如果你不想用户名和密码被别人看见,也比较简单,单独写一个程序集,将用户名,密码等写到一个类中,然后用我的教程中的 C# 代码混淆加密的方式把 dll 加密就行了,在 Visual Studio 2022 中反编译也是看不到的,而且,其他的反编译软件也是没用的,但是在程序运行时,用户名和密码是可以正常的读出来的。

我当前配置文件中的 用户名、密码、ftp服务器链接,主程序名 如下所示

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <configuration>
  3. <startup>
  4. <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
  5. </startup>
  6. <appSettings>
  7. <add key="FtpUrl" value="ftp://127.0.0.1//"/>
  8. <add key="FtpUser" value="user"/>
  9. <add key="FtpPassword" value="123456"/>
  10. <add key="MainProgram" value="CNCMain"/>
  11. </appSettings>
  12. </configuration>

3.读取 FTP 文件列表

在这里,我一次性将 FTP 链接中对应目录的所有文件的 文件名,文件大小,文件哈希值,文件路径,文件夹,包含子目录文件,都读出来了,这样就没必要和之前一样,单独去搞配置文件了。

4.读取本地文件

读取本地文件是为了判断哪些文件需要替换,删除,那些文件夹需要创建,删除,总之就是让客户端这边需要更新的文件和服务器一样,没有多余的文件,也能够保持所有的文件是最新的版本。

5.匹配更新

有了 FTP 服务器对应目录的文件数据,也有了本地目录的所有文件数据,接下来就是进行匹配了,找出哪些需要创建的文件夹,需要删除的文件夹,需要更新的文件,需要删除的文件,这里匹配文件的用法依然使用哈希值匹配。

  1. using System.Collections.Generic;
  2. internal class UpdateResultInfo
  3. {
  4. /// <summary>
  5. /// 需要创建的文件夹列表
  6. /// </summary>
  7. public List<string> CreateFolderList { get; set; } = new List<string>();
  8. /// <summary>
  9. /// 需要删除的文件夹列表
  10. /// </summary>
  11. public List<string> DeleteFolderList { get; set; } = new List<string>();
  12. /// <summary>
  13. /// 本地需要更新的文件列表
  14. /// </summary>
  15. public List<DownloadFileInfo> LocalUpdateFileList { get;set; } = new List<DownloadFileInfo>();
  16. /// <summary>
  17. /// 本地需要删除的文件列表
  18. /// </summary>
  19. public List<FileInfo> LocalDeleteFileList { get; set; } = new List<FileInfo>();
  20. }

这里会单独写一个方法来得出想要的结果,然后由单独的方法去处理这些结果。

下面是控制台效果,不喜欢也可以去掉,由于本地只有 update.exe 文件,而 update.exe 又在黑名单中,所以默认会把 ftp 服务器对应目录的所有文件下载下来,如果服务器文件和客户端文件是一样的,那么这个文件是不会下载的,这个我经过测试,是没问题的。

界面效果

6.版本的切换

版本的切换也比较简单,在配置文件中,改对应的链接就好了,客户端就会自动和服务器对应的版本进行对比了。

比如:

ftp://127.0.0.1//v1.0.1
ftp://127.0.0.1//v1.0.2
ftp://127.0.0.1//v1.0.3

那么关于 FTP 自动更新的流程就是这个样子了,上面的功能,都是经过各种测试,花了一些时间写出来的,流程是可以走的通的,有兴趣的朋友也可以自己写写看,感觉 FTP 版,要比我之前写的 HTTP 版的要简单很多。

代码我并没有全部贴出来,有需要的可以去支持一下我,在此谢谢了,有源码有疑问的可以私信我,我看到后会回复的。

源码地址:点击下载

三、环境搭建

搭建 IIS 版 FTP 服务器

参考帖子:

【Windows】之搭建 FTP 服务器_windows搭建ftp服务器-CSDN博客

虽然框架是可用的,但还是要注意以下几点:

1.FTP服务器搭建完成后,如果你的电脑IP地址变了,记得更改,否则客户端会访问不了。

2.FTP 文件夹名字,尽量不要用空格,因为在访问的时候,是一个链接形式进行访问的,链接中有空格可能会导致无法访问。

查看 FTP 的链接地址:

防火墙

在使用之前,FTP服务端电脑记得关闭防火墙,一定要保证客户端的电脑能 ping 的通

FTP 登陆测试

为了检测 FTP 是否能连接的上,最好先在文件管理器和网页进行测试,下面我都演示下。

1)使用浏览器,在浏览器输入:ftp://192.168.30.83/(你自己的FTP服务器地址),注意 ip 地址后面使用的是单斜杠

然后回车,就会弹框,让你输入用户名和密码

这时候,输入你创建 FTP用的 windows 账号就行了。

输入完成,就能看到你的 FTP 服务器文件了

2)使用文件管理器

使用文件管理器操作差不多,输入地址,按回车就行了

输入账号和密码

打开了 FTP 的根目录,说明 FTP 服务器正常使用。

四、常见问题

问题1:界面不动

如果界面一直停留在这个界面不动,一般情况是配置文件中的 FTP 地址,账号和密码配置出了问题,这时候,首先检查配置文件的数据是否正确。

如果还是无法读取 FTP 服务器的文件,那就先在浏览器中查看 FTP 服务器是否能连接上,如果 FTP 服务器中能连接,那么就把源码复制到客户端中,用 Visual Studio 2022 进行打开断点查看,一般来说,在浏览器中能查看 FTP 服务器,当前软件也可以连接的上。

2023.12.30 更新

针对当前的源码进行大量优化,两个项目对比:

除优化外,同时删除了一部功能,改动如下:

1.项目从 Winform 改成了控制台应用

2.配置文件从 App.config 里读取,改为读取自定义的配置文件 ftpAccount.config

3.修复了本地黑名单文件和黑名单文件夹内文件重复下载问题

4.匹配文件算法重写

5.去掉了删除本地多余的文件夹和文件功能(除了要更新的文件,其他文件和文件夹都不会被删除)

6.将黑名单文件和黑名单文件夹的读取放入到了本地的 txt 文件中,如下:

黑名单和黑名单文件夹一样,文件夹直接写文件夹名字,文件的话要加后缀,比如 xxx.exe,用换行作为区分

当前的源码如果遇到 bug 或者有更好的建议,欢迎私信或者评论,我会改正过来,然后更新资源文件,谢谢。

源码地址:点击下载

结束

end

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/199839
推荐阅读
相关标签
  

闽ICP备14008679号