公司开发了Winform程序装了几十台设备,手工更新非常麻烦,为了方便程序更新,自己开发了一个基于ftp的自动更新插件,可以设置版本,启动程序,更新时间,下面就是效果图。
把Update.exe和UpdateConfig.xml文件放到你的程序目录然后根据你项目的需求编写xml配置文件
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(""))
{
}
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文件