`
vv_1024
  • 浏览: 111657 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

B/S和C/S架构的融合,软件客户端通过WebService接口达到自动更新和上传数据

阅读更多

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接口

package org.vv.hr.webservice.extra;

/**
 * ISendFileWS WebService 接口
 * 
 * @author 俞立全
 * @date 2009-06-16
 */
public interface ISendFileWS {

	/**
	 * 读取文件大小
	 * 
	 * @param fileName
	 * @return
	 */
	public long getFileSize(String fileName);

	
	/**
	 * 客户端调用此方法,分块获取更新文件数据
	 * 
	 * @param fileName 文件名
	 * @param offset 偏移值
	 * @param bufferSize 每次获取的大小
	 * @return 字节数组
	 */
	public byte[] getUpdateFile(String fileName, int offset, int bufferSize);

	/**
	 * 以流形式上传文件至服务器
	 * 
	 * @param fs
	 * @param fileName
	 * @return 服务器存储路径
	 */
	public String uploadFile(byte[] fs, String fileName);

	/**
	 * 从服务器端以流形式下发文件
	 * 
	 * @param path
	 * @return
	 */
	public byte[] downFile(String path);

}

 

 

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

 

客户端: 更新进度条采用多线程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更新配置文件的信息如下:

 

<?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),设置该值,对传输速度影响很大,环境越是恶劣,丢包明显,把值设小,可以加大稳定性。环境好,可以加大数值,加快传输速度。

private static int 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 的性能优化方式很多,这里不进行讨论了。

  • 大小: 29 KB
1
0
分享到:
评论

相关推荐

    基于WebService和Android的C_S B_S结构手机电影系统.pdf

    4. **系统特性与优势**:通过使用WebService,系统能够跨越不同的平台进行通信,实现了C/S和B/S架构的融合,既保留了C/S架构的高效性和直接性,又利用了B/S架构的便捷性和可扩展性。此外,系统的分布式异构数据库...

    webservice + spring + hibernate

    【整合应用】:在“webservice + spring + hibernate”的整合应用中,Spring作为核心框架,负责管理和协调各个组件,包括数据库操作(通过Hibernate)和Web服务接口(可能是通过Spring的WebService模块,如JAX-WS)...

    pb webservice

    通过这个"pb webservice"的例子,开发者不仅能学习到如何在PowerBuilder中创建Web服务,还能了解到如何将它们集成到实际应用中,从而实现跨平台的数据交换和功能调用。这对于提升PB开发者的技术水平,尤其是与现代...

    iPhone & Android 智能手机客户端软件技术方案

    在手机客户端软件的整体设计中,方案提出了设计原则,其中包括原始需求原则、技术架构的先进性和接口的开放性标准性。设计目标是确保软件的稳定性、安全性、客户端性能和用户体验。 系统模块架构方面,手机客户端...

    IVMS-5000平台软件软件功能介绍.doc

    - 应用子系统:通过WebService接口,提供C/S、B/S和手机客户端等多种形式的应用,优化用户体验。 1.3 平台组成 IVMS安防集中管理平台由以下核心模块组成: - 中心管理服务:资源、用户、报警、录像和网络管理,以及...

    基于WCF的分布式缓存系统设计.pdf

    本文所讨论的系统是一个客户端为桌面应用程序,服务端为WCF应用程序的系统,它同时具有B/S和C/S的双重特性。这种架构使得系统可以在客户端和服务器端都实现缓存设计,即“双重缓存”机制。 在客户端访问数据时,...

    基于Dubbo+Zookeeper实现webservice

    综上所述,"基于Dubbo+Zookeeper实现webservice"是一个融合了现代微服务理念和技术传统Web服务需求的实践案例。通过这种方式,我们可以构建一个高可用、可扩展的服务网络,同时兼容不同类型的客户端。提供的文档应...

    WebService学习笔记

    它的核心理念是服务导向架构(SOA),即通过服务的方式实现应用之间的解耦和互操作性。 在传统应用程序开发中,尤其是在分布式环境里,如Windows富客户端应用程序,往往依赖于特定的技术,如DCOM,来进行服务器间的...

    apache-cxf_WebService

    - **强大的数据绑定**:CXF支持JAXB和XMLBeans作为默认的数据绑定机制,同时也可以与其他数据绑定框架如XStream、Gson集成,处理JSON和其他非XML格式的数据。 - **传输协议**:除了HTTP/S,CXF还支持其他传输协议,...

    WebService技术手册 CXF&XFire

    - 定义服务接口:使用Java编程接口定义服务,或者通过WSDL文件自动生成Java接口。 - 实现服务:编写实现服务接口的Java类。 - 创建服务端点:配置CXF的服务器端点,将服务接口与实现绑定。 - 发布服务:使用CXF...

    CTSS:一个综合实训选题系统,采用CS架构,客户端有PC端和Android端。使用AIXS2和KSOAP框架提供webservice服务,解决异构平台访问数据库不统一

    总的来说,CTSS是一个融合了C/S架构、WebService技术和跨平台通信策略的实训选题系统,它的设计和实现充分考虑了异构环境下的数据一致性,是Java技术在实践中的一个典型应用案例。对于学习和研究Java开发、Web服务...

    Axis2WebService与Spring的整合

    在IT行业中,Web服务是应用程序之间进行通信的一种标准方式,而Axis2是Apache软件基金会开发的一个流行的Java Web服务框架。Spring框架则是Java企业级应用的事实标准,提供了强大的依赖注入、AOP(面向切面编程)等...

    北大Java--WebService

    【Java与WebService】是计算机科学领域中的一种技术融合,它主要涉及如何利用XML(可扩展标记语言)在不同系统间实现数据交换和互操作性。在北大Java课程的第十七部分,这一主题深入探讨了WebService在当今信息化...

    webservice构建列车查询系统

    总的来说,"webservice构建列车查询系统"是一个融合了Web服务技术、数据库管理和软件工程实践的项目,对于学习和提升IT技能,尤其是面向服务架构(SOA)的理解,具有很高的实践价值。通过这个项目,开发者不仅能掌握...

    webservice xfire jar包 spring

    Web服务在IT行业中扮演着重要的角色,允许不同的系统和应用程序之间进行数据交换和通信。而XFire是一个已不再维护的开源Java Web服务框架,它曾经是Apache CXF的一部分。Spring框架,另一方面,是一个广泛使用的Java...

    Summit架构分析1

    7. **技术栈**:Summit系统利用了多种编程语言和框架,如C#、Java、C/C++,以及.NET和WebService技术,体现了多技术融合的特点。 通过这种架构设计,Summit能够提供高效的计算能力,同时保持良好的可扩展性和互操作...

    webservice 简单实例

    标题“webservice 简单实例”中蕴含的知识点,首先要介绍Web服务(WebService)的基本概念,它是一种基于网络的、分布式的模块化组件,它执行特定的任务,遵守特定的接口规范编码,语言和协议独立。Web服务可让不同...

    基于SVG的实时数据动态发布模型

    针对这一问题,前人研究中提出的“隐藏页面法”虽能一定程度上解决实时数据更新问题,但仍存在服务器端与客户端耦合紧密、数据重用性差等问题。为此,本文提出一种基于SVG的实时数据动态发布模型,旨在提高数据更新...

    基于嵌入式webservice技术远程抄表及监控系统

    综上所述,基于嵌入式Web Service技术的远程抄表及监控系统是现代工业自动化和信息化的重要组成部分,它结合了先进的软件技术和实际应用需求,为电力和其他行业的数据管理和监控提供了高效、安全的解决方案。

Global site tag (gtag.js) - Google Analytics