Winform使用FTP实现自动更新

项目背景

公司开发了Winform程序装了几十台设备,手工更新非常麻烦,为了方便程序更新,自己开发了一个基于ftp的自动更新插件,可以设置版本,启动程序,更新时间,下面就是效果图。

 项目使用

把Update.exe和UpdateConfig.xml文件放到你的程序目录然后根据你项目的需求编写xml配置文件

<?xml version="1.0" encoding="utf-8" ?>
<configs>
  <config name="version" value="1.02" />
  <config name="ftpUrl" value="127.0.0.1/Update/DyePlanQuery/Current" />
  <config name="exclude" value="WinFormsApp.exe.config,WinFormsApp.vshost.exe.config" />
  <config name="runApp" value="WinFormsApp.exe" />
  <config name="clearApp" value="WinFormsApp.exe" />
</configs>

vesrion是版本号,ftpUrl是你服务器FTP的路径,我是基于IIS搭建的FTP

 服务器也有个UpdateConfig.xml,程序会根据客户端和服务器的xml比对来更新程序。

 runApp配置之后可以再更新完之后自动启动应用程序,配置的是应用程序exe的名称

工程介绍

工程包含了ConfigHelper,FTPClient,Tools三个主要实现类

MainForm是负责获取下载清单,批量下载,并且显示下载的进度条

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    private void MainForm_Shown(object sender, EventArgs e)
    {
        if (string.IsNullOrEmpty(ConfigHelper.config.ftpUrl))
        {
            MessageBox.Show("参数初始化失败!");
            this.Close();
            return;
        }
        FTPClient fTPClient = new FTPClient(ConfigHelper.config.ftpUrl);

        if (!fTPClient.IsNeedUpdate())
        {
            MessageBoxButtons messButton = MessageBoxButtons.OKCancel;
            DialogResult dr = MessageBox.Show("没有检测到新版本,是否继续更新?", "信息", messButton);

            if (dr == DialogResult.OK)//如果点击“确定”按钮
            {

            }
            else//如果点击“取消”按钮
            {
                this.Close();
                return;
            }
        }

        var fileList = fTPClient.GetFileList();

        int totalCount = fileList.Count();
        for (var i = 0; i < totalCount; i++)
        {
            string fileInfo = fileList[i];
            string[] fileInfoArry = fileInfo.Split(' ');
            string fileName = fileInfoArry[fileInfoArry.Count() - 1];

            //如果是文件夹
            if (fileInfo.Contains("<DIR>"))
            {

            }
            else
            {
                if (!fileName.Contains("Update.exe"))
                {
                    var PrintingProduction = fTPClient.Download(fileName);
                    if (!PrintingProduction)
                    {
                        MessageBox.Show(fileName + "文件未更新请重新下载!");
                        return;
                    }
                }
            }
            System.Threading.Thread.Sleep(100);
            progressBar1.Value = i * 100 / totalCount;
        }
        progressBar1.Value = 100;
        //MessageBox.Show("更新成功!");

        Tools.RunApp();

        this.Close();
        Application.Exit();
    }

   
}

ConfigHelper主要功能是读取XML配置文件

 

public static class ConfigHelper
{
    public static Config config = null;
  
    //初次加载本地配置文件
    public static void Load()
    {
        XmlDocument doc = new XmlDocument();
        doc.Load("UpdateConfig.xml");

        config = ReadXml(doc);
    }
    //读取xml
    public static Config ReadXml(XmlDocument doc)
    {
        Config read_config = new Config();
        var nodeList = doc.SelectNodes(@"configs/config");

        foreach (XmlNode items in nodeList)
        {
            if (items.Attributes["name"].Value == "version")
            {
                read_config.version = items.Attributes["value"].Value;
            }
            else if (items.Attributes["name"].Value == "ftpUrl")
            {
                read_config.ftpUrl = items.Attributes["value"].Value;
            }
            else if (items.Attributes["name"].Value == "runApp")
            {
                read_config.runApp = items.Attributes["value"].Value;
            }
            else if (items.Attributes["name"].Value == "exclude")
            {
                if (!string.IsNullOrEmpty(items.Attributes["value"].Value))
                {
                    read_config.exclude = items.Attributes["value"].Value.Split(',').ToList();
                }
            }
            else if (items.Attributes["name"].Value == "clearApp")
            {
                if (!string.IsNullOrEmpty(items.Attributes["value"].Value))
                {
                    read_config.clearApp = items.Attributes["value"].Value.Split(',').ToList();
                }
            }
        }
        return read_config;
    }
}

FTPClient主要功能是和服务器FTP通讯

 

public class FTPClient
{
    //ftp路径
    public string ftpUrl = "";
    //构造函数
    public FTPClient(string ftpUrl)
    {
        this.ftpUrl = "ftp://" + ftpUrl;
    }
    //获取文件列表
    public string[] GetFileList()
    {
        try
        {
            StringBuilder result = new StringBuilder();//如果要修改字符串而不创建新的对象,则可以使用 System.Text.StringBuilder 类。
            FtpWebRequest ftp;
            ftp = (FtpWebRequest)FtpWebRequest.Create(new Uri(ftpUrl));
            //ftp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);
            ftp.Method = WebRequestMethods.Ftp.ListDirectoryDetails;//目录
            WebResponse response = ftp.GetResponse();
            StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.Default);//读入responses所创建的数据流
            string line = reader.ReadLine();//输入流中的下一行;如果到达了输入流的末尾,则为空引用
            while (line != null)
            {
                result.Append(line);//)Append 方法可用来将文本或对象的字符串表示形式添加到由当前 StringBuilder 对象表示的字符串的结尾处。
                result.Append("\n");
                line = reader.ReadLine();
            }
            result.Remove(result.ToString().LastIndexOf("\n"), 1);//移除最后的换行》?
            reader.Close();
            response.Close();
            return result.ToString().Split('\n');
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
    //下载
    public bool Download(string fileUrl)
    {
        //是否排除
        if (ConfigHelper.config.exclude.Contains(fileUrl))
        {
            return true;
        }
        FtpWebRequest reqFtp = null;
        FtpWebResponse response = null;
        Stream ftpStream = null;
        FileStream outputStream = null;
        try
        {
            string newFileName = System.IO.Directory.GetCurrentDirectory() + "/" + fileUrl;//给原名字
            if (File.Exists(newFileName))
            {
                try
                {
                    File.Delete(newFileName);//如果已有则删除已有
                }
                catch { }

            }

            string url = ftpUrl + "/" + fileUrl;
            //URL是URI的子集,统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。统一资源定位符URL就是用定位的方式实现的URI。
            //URI可被视为定位符(URL),名称(URN)或两者兼备。统一资源名(URN)如同一个人的名称,而统一资源定位符(URL)代表一个人的住址。
            //换言之,URN定义某事物的身份,而URL提供查找该事物的方法。
            reqFtp = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));//创造连接,创建一个FtpWebRequest对象,指向ftp服务器的uri
            reqFtp.UseBinary = true;//二进制传输,给FtpWebRequest对象设置属性
            reqFtp.Method = WebRequestMethods.Ftp.DownloadFile;// 设置ftp的执行方法(上传,下载等)

            //reqFtp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);//账户密码
            response = (FtpWebResponse)reqFtp.GetResponse();//获得文件,执行请求
            ftpStream = response.GetResponseStream();//获得流,接收相应流
            long cl = GetFileSize(url);//获得文件大小cl
            int bufferSize = 2048;//时间延迟
            int readCount;
            byte[] buffer = new byte[bufferSize];
            readCount = ftpStream.Read(buffer, 0, bufferSize);//从流里读取的字节数为0时,则下载结束。
            outputStream = new FileStream(newFileName, FileMode.Create);//输出流

            while (readCount > 0)
            {
                outputStream.Write(buffer, 0, readCount);//写入
                readCount = ftpStream.Read(buffer, 0, bufferSize);//写入
            }
            return true;
        }
        catch (Exception ex)//捕捉
        {
            throw;
        }
        finally//异常清理,无论是否异常,都会执行
        {
            if (reqFtp != null)
            {
                reqFtp.Abort();
            }
            if (response != null)
            {
                response.Close();
            }
            if (ftpStream != null)
            {
                ftpStream.Close();
            }
            if (outputStream != null)
            {
                outputStream.Close();
            }
        }
    }
    //获取文件大小
    public static long GetFileSize(string url)
    {
        long fileSize = 0;
        try
        {
            FtpWebRequest reqFtp = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));//路径创造新的连接
            reqFtp.UseBinary = true;
            //reqFtp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);//连接
            reqFtp.Method = WebRequestMethods.Ftp.GetFileSize;//方法大小
            FtpWebResponse response = (FtpWebResponse)reqFtp.GetResponse();
            fileSize = response.ContentLength;//获得大小

            response.Close();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
        return fileSize;
    }
    //获取服务器配置文件
    public Config GetServerConfig()
    {
        Config serverConfig = null;
        string fileUrl = "UpdateConfig.xml";
        FtpWebRequest reqFtp = null;
        FtpWebResponse response = null;
        Stream ftpStream = null;
        FileStream outputStream = null;
        try
        {
            string url = ftpUrl + "/" + fileUrl;
            //URL是URI的子集,统一资源标志符URI就是在某一规则下能把一个资源独一无二地标识出来。统一资源定位符URL就是用定位的方式实现的URI。
            //URI可被视为定位符(URL),名称(URN)或两者兼备。统一资源名(URN)如同一个人的名称,而统一资源定位符(URL)代表一个人的住址。
            //换言之,URN定义某事物的身份,而URL提供查找该事物的方法。
            reqFtp = (FtpWebRequest)FtpWebRequest.Create(new Uri(url));//创造连接,创建一个FtpWebRequest对象,指向ftp服务器的uri
            reqFtp.UseBinary = true;//二进制传输,给FtpWebRequest对象设置属性
            reqFtp.Method = WebRequestMethods.Ftp.DownloadFile;// 设置ftp的执行方法(上传,下载等)

            //reqFtp.Credentials = new NetworkCredential(FTPUSERNAME, FTPPASSWORD);//账户密码
            response = (FtpWebResponse)reqFtp.GetResponse();//获得文件,执行请求
            ftpStream = response.GetResponseStream();//获得流,接收相应流

            //读取XML文件
            XmlDocument doc = new XmlDocument();
            doc.Load(ftpStream);

            serverConfig = ConfigHelper.ReadXml(doc);

        }
        catch (Exception ex)//捕捉
        {
            throw;
        }
        finally//异常清理,无论是否异常,都会执行
        {
            if (reqFtp != null)
            {
                reqFtp.Abort();
            }
            if (response != null)
            {
                response.Close();
            }
            if (ftpStream != null)
            {
                ftpStream.Close();
            }
            if (outputStream != null)
            {
                outputStream.Close();
            }
        }
        return serverConfig;
    }
    //判断是否需要更新
    public bool IsNeedUpdate()
    {
        bool _bool = true;
        Config serverConfig = GetServerConfig();
        if (serverConfig.version == ConfigHelper.config.version)
        {
            _bool = false;
        }
        return _bool;
    }
   
}

Tools包含了一些公共的方法

public class Tools
{
    static void RunAppThread()
    {
        string url = System.IO.Directory.GetCurrentDirectory() + "\\" + ConfigHelper.config.runApp;
        Process.Start(url);
    }
    //启动APP
    public static void RunApp()
    {
        if (!string.IsNullOrEmpty(ConfigHelper.config.runApp))
        {
            System.Threading.Thread oThread = new System.Threading.Thread(new System.Threading.ThreadStart(RunAppThread));
            oThread.Start();
        }
    }
    //删除APP
    public static void ClearRunApp()
    {
        if (ConfigHelper.config.clearApp.Count > 0)
        {
            foreach (var items in ConfigHelper.config.clearApp)
            {
                System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcessesByName(items);
                foreach (System.Diagnostics.Process process in processes)
                {
                    process.Kill();
                }
            }
        }
    }
}

 项目中使用

首先把update.exe引入工程

using BrickDogUpdate = Update;

在项目中添加定时器,设置自己想要更新的时间点,如果服务器有更新就会关闭当前程序启动更新程序,更新完毕会再次启动程序。

private void timer1_Tick(object sender, EventArgs e)
{
    //更新 23:59:59 11:59:59
    if (DateTime.Now.ToString("HH:mm:ss") == "23:59:59" || DateTime.Now.ToString("HH:mm:ss") == "11:59:59")
    {
        //加载配置文件
        BrickDogUpdate.ConfigHelper.Load();
        BrickDogUpdate.FTPClient fTPClient = new Update.FTPClient(BrickDogUpdate.ConfigHelper.config.ftpUrl);
        //与FTP服务器比较版本号是否需要更新
        if (fTPClient.IsNeedUpdate())
        {
            Start_Update_Thread();
            Application.Exit();
        }
    }
}

void Start_Update_Thread()
{
    System.Threading.Thread oThread = new System.Threading.Thread(new System.Threading.ThreadStart(Update_Thread));
    oThread.Start();
}

void Update_Thread()
{
    string updateUrl = System.IO.Directory.GetCurrentDirectory() + "\\Update.exe";
    Process.Start(updateUrl);
}

其他

.vshost的进程会导致进程关闭不彻底,我们如下配置。

在调试页面,改成release,同时取消最后一项启用承载进程

 在生成页面,将高级选项中的调试信息改成none可以取消生成pdb文件

猜你喜欢

转载自blog.csdn.net/qq243348167/article/details/126623611