zoukankan      html  css  js  c++  java
  • B/S和C/S架构的融合,软件客户端通过WebService接口达到自动更新和上传数据

    转载 http://lovephoenix.iteye.com/blog/593078

    B/S和C/S架构的融合——软件客户端通过WebService接口达到自动更新和上传数据,支持任意客户端语言环境。

    测试用例:打开客户端自动下载更新文件,上传照片 

    服务器环境:Tomcat 6 、eclipse 3,测试 WebService 采用 spring 2.5 + xfire 1.2.6 ,目前可升级为 cxf 2.2.3 

    客户端环境:Microsoft VS2008 采用C#语言。 

    构建服务器 

    WS接口(Java代码)

    1.package org.vv.hr.webservice.extra;  
    2.
    3./**
    4. * ISendFileWS WebService 接口
    5. *
    6. *
    @author 俞立全
    7. * @date 2009-06-16
    8.
    */
    9.public interface ISendFileWS {
    10.
    11. /**
    12. * 读取文件大小
    13. *
    14. *
    @param fileName
    15. *
    @return
    16.
    */
    17. public long getFileSize(String fileName);
    18.
    19.
    20. /**
    21. * 客户端调用此方法,分块获取更新文件数据
    22. *
    23. *
    @param fileName 文件名
    24. *
    @param offset 偏移值
    25. *
    @param bufferSize 每次获取的大小
    26. *
    @return 字节数组
    27.
    */
    28. public byte[] getUpdateFile(String fileName, int offset, int bufferSize);
    29.
    30. /**
    31. * 以流形式上传文件至服务器
    32. *
    33. *
    @param fs
    34. *
    @param fileName
    35. *
    @return 服务器存储路径
    36.
    */
    37. public String uploadFile(byte[] fs, String fileName);
    38.
    39. /**
    40. * 从服务器端以流形式下发文件
    41. *
    42. *
    @param path
    43. *
    @return
    44.
    */
    45. public byte[] downFile(String path);
    46.
    47.}

    实现代码很简单,各有各的写法,下面重点介绍思想。

    客户端(C#代码 )

    更新进度条采用多线程ui,完整代码如下:

     

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.IO;
    using System.Xml;
    using System.Threading;

    namespace HR_update
    {
    public partial class update : Form
    {
    /// <summary>
    /// 每次下载并写入磁盘的文件数据大小(字节)
    /// </summary>
    private static int BUFFER_SIZE = 128 * 1024;

    //把窗体改为单态模型
    private static update updateForm;
    public static update getUpdateForm()
    {
    if (updateForm == null)
    {
    updateForm = new update();
    }
    return updateForm;
    }
    //构造函数改为私有,外部程序不可以使用 new() 来创建新窗体,保证了窗体唯一性
    private update()
    {
    //不检查线程间操作,容许子线呈随时更新ui,微软已经不推荐使用,这里用 invoke 回调代替
    //CheckForIllegalCrossThreadCalls = false;
    InitializeComponent();
    }


    //******** 定义代理方法,解决多线程环境中跨线程改写 ui 控件属性。start ********

    //定义设置一个文本的委托方法(字符串)
    private delegate void setText(string log);
    //定义设置一个进度的委托方法(整型)
    private delegate void setProcess(int count);

    //设置总进度条的最大数
    private void setProgressBar1_Maximum(int count)
    {
    progressBar1.Maximum = count;
    }
    //设置单文件进度条的最大数
    private void setProgressBar2_Maximum(int count)
    {
    progressBar2.Maximum = count;
    }
    //设置总进度条的当前值
    private void setProgressBar1_value(int count)
    {
    progressBar1.Value = count;
    }
    //设置单文件进度条当前值
    private void setProgressBar2_value(int count)
    {
    progressBar2.Value = count;
    }
    //设置总文件进度条步进进度
    private void addProgressBar1_value(int count)
    {
    progressBar1.Value += count;
    }
    //设置单文件进度条步进进度
    private void addProgressBar2_value(int count)
    {
    progressBar2.Value += count;
    }
    //设置文本框的值
    private void UpdateText(string log)
    {
    textBox1.Text += log;
    }

    //******** 定义代理方法,解决多线程环境中跨线程改写 ui 控件属性。 end ********

    /// <summary>
    /// 窗体显示时,调用 invokeThread 方法
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void update_Shown(object sender, EventArgs e)
    {
    invokeThread();
    }

    /// <summary>
    /// 开启一个线程,执行 update_function 方法
    /// </summary>
    void invokeThread()
    {
    Thread th = new Thread(new ThreadStart(update_function));
    th.Start();
    }

    /// <summary>
    /// 自动更新方法,整合实现下面的业务逻辑。
    /// </summary>
    private void update_function()
    {
    //判断 位于本地客户端程序文件夹 update 是否存在
    if (Directory.Exists(Application.StartupPath + "/update"))
    {
    //存在则删除,true 表示移除包含的子目录及文件
    Directory.Delete("update/", true);
    }
    //通过 webservice 从服务器端获取更新脚本文件 update.xml
    getUpdateXMLFile();
    //判断强制更新开关
    if (isForceUpdate())
    {
    //通过 webservice 从服务器端下载更新程序文件
    downloadFiles();
    }
    else
    {
    //比较版本号
    if (verifyVersion())
    {
    //通过 webservice 从服务器端下载更新程序文件
    downloadFiles();
    }
    }
    //启动客户端主程序,退出更新程序
    appExit();
    }

    /// <summary>
    /// 下载 update.xml
    /// </summary>
    private void getUpdateXMLFile()
    {
    //执行委托方法,更新文本控件内容
    textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在从服务器下载 更新脚本文件 update.xml \r\n" });
    //创建一个文件传送的 webservice 接口实例
    SendFileWS.ISendFileWS sendFileWS = new HR_update.SendFileWS.ISendFileWS();
    //通过 webservice接口 获取服务器上 update.xml 文件的长度。
    long fileSize = sendFileWS.getFileSize("update.xml");
    //判断本地客户端文件夹下 update 目录是否存在
    if (!Directory.Exists(Application.StartupPath + "/update"))
    {
    //不存在则创建 update 目录
    Directory.CreateDirectory(Application.StartupPath + "/update");
    }
    //通过定义文件缓冲区分块下载 update.xml 文件
    for (int offset = 0; offset < fileSize; offset += BUFFER_SIZE)
    {
    //从服务器读取指定偏移值和指定长度的二进制文件字符数组
    byte[] bytes = sendFileWS.getUpdateFile("update.xml", offset, BUFFER_SIZE);
    //如果 字符数组不为空
    if (bytes != null)
    {
    //以追加方式打开 update.xml 文件
    using (FileStream fs = new FileStream(Application.StartupPath + "/update/update.xml", FileMode.Append))
    {
    //写入数据
    fs.Write(bytes, 0, bytes.Length);
    fs.Close();
    }
    }
    }

    }

    /// <summary>
    /// 是否开启强制更新。
    /// </summary>
    /// <returns>true 开启强制更新,false 比较版本号后再更新</returns>
    private bool isForceUpdate()
    {
    try
    {
    //开始解析 update/update.xml 新文件
    XmlDocument doc = new XmlDocument();
    doc.Load("update/update.xml");
    XmlElement root = doc.DocumentElement;
    //节点是否存在
    if (root.SelectSingleNode("forceUpdate") != null)
    {
    //获取 forceUpdate 节点的内容
    string forceUpdate = root.SelectSingleNode("forceUpdate").InnerText;
    doc = null;
    if (forceUpdate.Equals("true"))
    {
    textBox1.Invoke(new setText(this.UpdateText), new object[] { "强制更新开关已打开,不再匹配版本号。 \r\n" });
    return true;
    }
    else
    {
    return false;
    }
    }
    else
    {
    doc = null;
    return false;
    }


    }
    catch
    {
    //发生异常,则更新程序,覆盖 update.xml
    MessageBox.Show("版本文件解析异常,服务器端 update.xml 可能已经损坏,请联系管理员。","警告",MessageBoxButtons.OK,MessageBoxIcon.Warning);
    return true;
    }
    }


    /// <summary>
    /// 解析 update.xml 文件,比较version 和 subversion 判断是否有新版本
    /// </summary>
    /// <returns>true 有新版本,false 版本相同</returns>
    private bool verifyVersion()
    {
    try
    {
    if (!File.Exists("update.xml"))
    {
    return true;
    }
    //开始解析 update.xml 旧文件
    XmlDocument doc1 = new XmlDocument();
    doc1.Load("update.xml");
    XmlElement root1 = doc1.DocumentElement;

    //开始解析 update/update.xml 新文件
    XmlDocument doc2 = new XmlDocument();
    doc2.Load("update/update.xml");
    XmlElement root2 = doc2.DocumentElement;

    if (root1.SelectSingleNode("version") != null && root1.SelectSingleNode("subversion") != null && root2.SelectSingleNode("version") != null && root2.SelectSingleNode("subversion") != null)
    {
    int old_version = Convert.ToInt32(root1.SelectSingleNode("version").InnerText);
    int old_subversion = Convert.ToInt32(root1.SelectSingleNode("subversion").InnerText);
    int new_version = Convert.ToInt32(root2.SelectSingleNode("version").InnerText);
    int new_subversion = Convert.ToInt32(root2.SelectSingleNode("subversion").InnerText);

    doc1 = null;
    doc2 = null;

    textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在判断版本号...\r\n" });
    //判断版本号和子版本号
    if (old_version == new_version && old_subversion == new_subversion)
    {
    return false;
    }
    else
    {
    textBox1.Invoke(new setText(this.UpdateText), new object[] { "发现新版本,开始读取更新列表 \r\n" });
    return true;
    }
    }
    else
    {
    textBox1.Invoke(new setText(this.UpdateText), new object[] { "无法解析版本号,将下载更新全部文件...\r\n" });
    doc1 = null;
    doc2 = null;
    return true;
    }
    }
    catch (Exception e)
    {
    //发生异常,则更新程序,覆盖 update.xml
    MessageBox.Show("版本文件解析异常,服务器端 update.xml 可能已经损坏,请联系管理员。", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    return true;
    }
    }

    /// <summary>
    /// 解析 update.xml,下载更新文件
    /// </summary>
    public void downloadFiles()
    {
    //解析 update.xml
    XmlDocument doc = new XmlDocument();
    doc.Load("update/update.xml");
    XmlElement root = doc.DocumentElement;
    XmlNode fileListNode = root.SelectSingleNode("filelist");
    //获取更新文件的数量
    int fileCount = Convert.ToInt32(fileListNode.Attributes["count"].Value);
    //调用委托方法,更新控件内容。
    textBox1.Invoke(new setText(this.UpdateText), new object[] { "更新文件数量 " + fileCount.ToString() + "\r\n" });
    progressBar1.Invoke(new setProcess(this.setProgressBar1_Maximum), new object[] { fileCount });

    //结束 HRClient.exe 进程?
    System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
    foreach (System.Diagnostics.Process process in processes)
    {
    if (process.ProcessName == "HRClient.exe")
    {
    process.Close();
    break;
    }
    }

    //循环文件列表
    for (int i = 0; i < fileCount; i++)
    {
    XmlNode itemNode = fileListNode.ChildNodes[i];
    //获取更新文件名
    string fileName = itemNode.Attributes["name"].Value;
    //调用委托方法,更新控件内容。
    textBox1.Invoke(new setText(this.UpdateText), new object[] { "正在下载文件 " + fileName + "\r\n" });
    //分块下载文件,调用 webservice 接口
    SendFileWS.ISendFileWS sendFileWS = new HR_update.SendFileWS.ISendFileWS();
    //获取文件长度(字节)
    long fileSize = sendFileWS.getFileSize(fileName);

    //调用委托方法,更新进度条控件内容。
    progressBar2.Invoke(new setProcess(this.setProgressBar2_Maximum), new object[] { (int)(fileSize / BUFFER_SIZE) + 1 });
    progressBar2.Invoke(new setProcess(this.setProgressBar2_value), new object[] { 0 });
    //通过 webservice 接口 循环读取文件数据块,每次向前步进 BUFFER_SIZE
    for (int offset = 0; offset < fileSize; offset += BUFFER_SIZE)
    {
    Byte[] bytes = sendFileWS.getUpdateFile(fileName, offset, BUFFER_SIZE);
    if (bytes != null)
    {
    //将下载的更新文件写入程序目录的 update 文件夹下
    using (FileStream fs = new FileStream(Application.StartupPath + "/update/" + fileName, FileMode.Append))
    {
    fs.Write(bytes, 0, bytes.Length);
    fs.Close();
    }
    }
    bytes = null;
    progressBar2.Invoke(new setProcess(this.addProgressBar2_value), new object[] { 1 });
    }
    //替换文件
    try
    {
    if (fileName != "HR_update.XmlSerializers.dll" || fileName != "HR_update.exe.config" || fileName != "HR_update.pdb" || fileName != "HR_update.exe")
    {
    File.Copy("update/" + fileName, fileName, true);
    }
    }
    catch
    {
    textBox1.Invoke(new setText(this.UpdateText), new object[] { "无法复制" + fileName + "\r\n" });
    }
    progressBar1.Invoke(new setProcess(this.addProgressBar1_value), new object[] { 1 });
    }
    //最后复制更新信息文件
    File.Copy("update/update.xml" , "update.xml", true);

    }

    /// <summary>
    /// 启动客户端主程序,退出更新程序
    /// </summary>
    private void appExit()
    {
    //获取主程序执行文件名
    XmlDocument doc = new XmlDocument();
    doc.Load("update.xml");
    XmlElement root = doc.DocumentElement;
    string executeFile = string.Empty;
    //节点是否存在
    if (root.SelectSingleNode("executeFile") != null)
    {
    //获取 executeFile 节点的内容
    executeFile = root.SelectSingleNode("executeFile").InnerText;
    }
    doc = null;
    //启动客户端程序
    System.Diagnostics.Process.Start(Application.StartupPath + @"\" + executeFile);
    //更新程序退出
    Application.Exit();
    }
    }
    }

    客户端启动更新截图如下:采用双进度条。

    xml代码:

    <?xml version="1.0" encoding="UTF-8"?>
    <update>
    <forceUpdate>false</forceUpdate>
    <version>20080104</version>
    <subversion>3</subversion>
    <filelist count="24">
    <file name="CommonLibrary.dll">true</file>
    <file name="CommonLibrary.pdb">true</file>
    <file name="HR_update.exe">true</file>
    <file name="HR_update.exe.config">true</file>
    <file name="HR_update.pdb">true</file>
    <file name="HR_update.XmlSerializers.dll">true</file>
    <file name="HRClient.exe">true</file>
    <file name="HRClient.exe.config">true</file>
    <file name="HRClient.pdb">true</file>
    </filelist>
    <executeFile>HRClient.exe</executeFile>
    </update>

    forceUpdate 节点默认为false ,如果设为true,表示每次更新不比较版本号,下载所有文件并覆盖。

    version 节点为主版本号

    subversion 节点为子版本号

    filelist 包含了更新的文件列表

    file name 为文件名 属性true表示需要更新。

    executeFile 为更新程序执行完成后自动调用的主应用程序文件名,这样更新程序和主程序完全解耦,可以应用到其它系统中。

    在客户端的源码中设置了每次向服务器请求的数据块大小。这也是在服务器端代码中我之前注释掉的地方,这行代码在实际应用中,应该放在客户端配置文件中,这个参数很有意义,经过测试,在不同的网络环境中(1000M、100M、10M),设置该值,对传输速度影响很大,环境越是恶劣,丢包明显,把值设小,可以加大稳定性。环境好,可以加大数值,加快传输速度。

    C#代码:

    private staticint BUFFER_SIZE = 128 * 1024;  

    客户端上传照片的接口代码如下,也是调用了服务器的 ws 接口

    WSFactory.getSendFileWS().uploadFile(commonBusiness.getBinaryFile(FileName), SafeFileName);  

    可以看到,J2EE  和 .NET 通过ws 还是可以很好的工作在一起的,J2EE 通过 ws 把 接口开放给 客户端,把原先客户端的公共业务逻辑放到了服务器来执行,客户端只是提交用户数据并接收服务器反馈的数据给用户,这使得客户端变得很轻,.net 提供了丰富的ui 界面组建,又大大弥补了传统J2EE B/S 架构用户界面的体验贫乏的特点。

    通过WS ,J2EE 还可以和任何支持 ws 接口的语言结合,如C++、vb 、甚至 windows7 自带的 PowerShell 脚本。

    这种方式灵活性很高,面对一个需求,可以有多种解决方案。可以把部分特殊业务放在客户端完成,服务器只提供权限,事务控制和持久化,也可以把业务放在服务器,客户端注意力放在用户体验上,如果单个业务繁重,还可以把服务器业务进行横向或纵向切分,多服务器节点之间通过ws传递数据。

    为了加快传递速度 ws 的性能优化方式很多,这里不进行讨论了。

  • 相关阅读:
    链表的常见操作(基础)
    插入排序
    五款超实用的开源SVG工具
    Code Project精彩系列
    微软Windows卸载工具可解决更新Silverlight不能成功安装问题
    SQL Server FOR XML PATH 语句的应用
    Windows 7下VS2008升级补丁
    瀚海拾贝(一)HTTP协议/IIS 原理及ASP.NET运行机制浅析【图解】
    Asp.net生成工作流、审批流的解决方案(asp.net workflow svg)
    利用Visual Studio制作安装包
  • 原文地址:https://www.cnblogs.com/jshchg/p/2278190.html
Copyright © 2011-2022 走看看