一、引言 随着Internet的发展,软件系统已经从客户服务器系统发展到服务器/浏览器系统,但是随着基于Internet应用的深化和基于Web计算的概念的提出,新的计算模型不再是简单的依赖于浏览器作为客户端。 首先,我们来简单看一下计算模型的发展历史,一开始,由于个人计算机还尚未普及,而且计算机价格都比较昂贵,我们一般采用主机模式来进行计算,也就是说客户实际上是通过终端和大型主机进行连接,主机分配一定的CPU时间和磁盘空间给用户,所有用户的计算实际上都是在主机上完成的。这样对主机的要求非常高,要求主机上的操作系统必须是高度可靠、安全的。在这个时候,流行的是IBM的大型机的操作系统,这里注意的是,即使到现在,很多银行依然通过这个模式进行运做,因为容易进行集中管理和维护,客户端实际上仅仅是一个终端的功能,从某一个角度讲就是主机屏幕的延伸。 随着分布式概念的提出和微机功能的增强,软件业又提出了客户/服务器的计算模型,把一些非关键的任务(比如图形界面的显示,数据的显示格式确定,数据的处理等等)放到客户端进行执行,这样相对就减轻了对服务器的负担。但是这种模式一般是基于局域网范围内进行的(比如在九十年代非常流行的一些基于数据库的信息系统就是根据这种模式构建的)。 随着Internet的发展和企业之间电子交互的需求的出现,出现了基于数据库/Web服务器/浏览器这样的计算模型,这种模型实际上是基于全球网络范围内进行的,客户端统一的以浏览器的形式表现给用户,用户通过HTTP协议把任务提交给Web服务器,Web服务器通过和数据库和应用服务器的交互把结果通过HTTP协议传递给客户端,然后客户端再通过浏览器显示结果。在这种模式下的关键是数据传递的安全性和事务性这两个问题,因为HTTP本质是是一个无状态的连接,所以事务处理就变得非常重要,同时因为整个业务是基于全球网络体系结构的,所以安全性也变成一个值得关注的问题。 随着Internet上计算任务的复杂化和业务的多样化,自然而然就产生了以Web为中心进行计算的需求。从本质上说,对Internet应用的复杂化使我们当前的操作系统都显得力不从心,因为我们当前的操作系统一般都是基于单机或者局域网系统的,而如何把操作系统扩展到整个Internet计算这个范畴内,就成了所有操作系统软件生产厂商所必须考虑的问题。 基于Web的软件系统的例子有很多,比如Napster,它允许在用户之间进行磁盘内容的共享,从某一种角度讲,它的基本概念是要建立一个基于Web的文件系统,这个文件系统包含了所有参与者本身的文件系统。这样参与者之间就可以进行文件的共享,通过输入一定的查询条件,我们就可以在其他的用户的存储设备上找到相应的文件并进行下载(当然这种概念在局域网中早就存在,现在Napster把这个概念扩展到了整个Internet领域而已)。 二、Web服务体系结构分析 这种基于Internet类型应用的出现使我们需要一个崭新的框架结构来进行程序的设计,我们需要一个快速和方便的方法进行代码的编写并且能够和Internet上其他的程序进行交互。当然在计算机之间进行数据和信息交互这个概念并不是很新,比如通过RPC,DCOM和CORBA等都可以实现不同计算机上的进程之间的交互。但是它们都有一个致命的缺点:它们需要进行交互的机器具有相似的系统,比如MSMQ只能和MSMQ进行对话,DCOM客户端只能和DCOM服务器端进行交互。 而我们真正需要的是一个通用的开发框架,也就是说不管系统的那一端是什么东西,我们这一端都可以和它进行信息的交互。它的本质意义就是说两端的操作系统不仅可以是异构的(比如一端可以是Windows,另一端可以是UNIX),而且实现的语言也可以是异构的(一端可以用C++实现,另一端可以用VB实现)。 如果大家还记得在DCOM进行交互时的数据列集(Marshal)这个概念的话,那么就可以理解基于Internet异构系统通讯的关键点是什么了。一个就是通讯的标准,两个进程需要采用标准的协议进行通讯,另外一个就是数据的打包,数据应该采用一致的形式进行打包和解包。当前基于Internet最流行的传输协议就是HTTP,所有的Web浏览器都通过这个协议和Web服务器进行通讯并得到相关的网页。而数据的打包也需要采用一定的标准,当前出现的跨平台的信息编码的标准就是XML。因为HTTP和XML都是工业的标准,并不和任何平台,厂商挂钩,所以基于这两种标准构建的系统无疑在任何环境是都是有生命力的。 为了创建一个Web服务,我们所需要做的工作就是编写一个.Net服务对象,使它被异地进程的调用就象能够被本地的客户端直接调用一样。实际上是通过给它标记一定的属性来实现的,使它能够被Web客户端所使用。通过ASP.NET,这个.Net服务对象就能够接受来自客户端的请求(通过HTTP协议传输的)。也就是说.Net服务对象能够和任何使用HTTP和XML标准的进程进行通讯,你也不需要考虑Web通讯的体系结构,操作系统已经帮你搞定了这一切。 从服务对象的角度来讲,一个客户和服务对象之间的通讯可以用下面的形式表示: 1. 从客户端的HTTP请求到达,其中参数可能包含在URL中,也可能包含在一个单独的XML文件中 2. ASP.NET根据.asmx文件的指定创建对象 3. ASP.NET调用对象的某一个特定的方法 4. 对象把结果返回给ASP.NET 在客户端,.NET提供了Proxy类用来快速方便的和服务器提供的Web服务进行交互,通过开发工具得到Web服务的描述,然后就可以产生一个包含一些功能函数的Proxy类,注意,在这里我们可以使用任何类型的语言来开发客户端,当客户端调用其中的某一个函数的时候,Proxy就会产生一个HTTP请求并把它发送给服务器,当服务器响应返回的时候,Proxy能够对结果进行解析并返回给调用该函数的客户端。这样,就保证了客户端能够通过HTTP和XML无缝的和Web服务器进行信息的交互。 从客户端的角度来讲,一个客户和服务对象之间的通讯可以用下面的形式表示: 1. 在运行时刻,客户端产生一个Proxy对象 2. 客户端调用Proxy中的一个方法 3. Proxy把调用转换成HTTP和XML形式,并通过Internet发送到服务器端 4. Proxy通过HTTP协议得到以XML形式表现的结果,并转化成相应的结果值返回给客户 三、Web服务的编写 这里我们可以写一个最简单的Web服务来说明这种新技术的使用,该Web服务以字符串的形式提供当前的服务器的时间(可以精确也可以不精确到秒)。程序是以标准的标记<%...%>"开始的,在该标记符内,WebService告诉ASP.NET该页的代码是作为一个Web服务出现的。Language告诉ASP编译这个页所使用的语言是VB,然后ASP.NET就会使用Visual Basic.NET来进行代码的编译。CLASS属性告诉ASP.NET当前类对象的名称为TimeService。 具体的代码如下: <%@ WebService Language="VB" Class="TimeService"%> ' 引入名字空间' 需要Web Service Imports System Imports System.Web.Services ' 建立一个新的类,该类必须继承系统提供的基类WebService Public Class TimeService : Inherits WebService '在类中建立我们所需要的函数,并标记为WebMethods Public Function <WebMethod()> GetTime (ShowSeconds as Boolean) As String ' 完成该函数的功能:发现当前的时间,格式化,并以字符串形式返回 Dim dt as DateTime If (ShowSeconds = TRUE) Then GetTime = dt.Now.ToLongTimeString Else GetTime = dt.Now.ToShortTimeString Endif End Function End Class 为了允许开发人员使用Web服务来开发客户端应用,需要在设计和开发的时候给他们提供一定的信息。比如,一个Web服务的客户端需要知道Web服务所暴露的方法和相关的参数以及所支持的协议,这个和一个标准的COM所携带的类型库的概念很类似。但是类型库是COM所专用的,而我们所提供的方法应该是和具体的体系结构无关的,所以需要编写一个通用的服务方法的描述。ASP.NET提供了一个可描述的服务,当编译一个WEB服务的时候,ASP.NET提供了一个文件列表用来说明服务所支持的协议,它所提供的方法和参数等等。这个文件是 XML形式编码并使用称为SDL(Service Descriptor Language)的语言进行描述的。可以通过http://WebServer/ specifiedDirectory /TimeService.asmx?SDL这样的形式来得到SDL语言。 当我们用VB或者VC来编写COM组件的时候,我们一般需要编写一个类型库来描述该COM组件所能提供的功能,而对ASP.NET来说,它能够自动的生成一个SDL文件,也可以先编写一个SDL文件,然后通过系统工具生成一个服务的模板文件。 一个典型的SDL文件具体内容如下: <!-- 标准的XML头,描述版本信息和其他相关信息--> <?xml version="1.0"?> <!-- 元素serviceDescription是文件的根节点,里面的内容是对Web服务的描述 --> <!-- 值TimeService表示服务的名称 --> <serviceDescription name="TimeService"> <!-- 元素serviceDescription包括两个子元素,一个是协议描述,另外一个是文档的schema描述 --> <!-协议描述用来告诉客户端开发人员我这个Web服务所支持的协议,并告诉他请求和响应数据的编码格式 --> <!-这里我们描述了HTTP GET的操作,一般有三种方法HTTP GET,HTTP POST和SOAP三种方法 --> <httpget> <service> <requestResponse name="GetTime" href="http://WebServer/specifiedDirectory/TimeService.asmx/GetTime"> <request> <param name="ShowSeconds"/> </request> <response> <mimeXml ref="s1:string"/> </response> </requestResponse> </service> </httpget> <!- 这里可以对其他的协议进行描述,比如httpget 和 soap -> <!- schema表示对服务的抽象定义,它和采用什么协议和它进行通讯无关 -> <!- 它实际上包含了对服务包含的函数的名称,参数和返回值的表示 -> <schema> <element name="GetTime"> <complexType> <element name="ShowSeconds" type="boolean"/> </complexType> </element> <element name="GetTimeResult"> <complexType> <element name="result" type="string" nullable="true"/> </complexType> </element> </schema> </serviceDescription> 四、Web服务的客户端的编写 现在让我们来看一下如何编写客户端的代码。我们知道ASP.NET实际上是在侦听三种类型的数据包,它们包括HTTP GET,HTTP POST和SOAP。 1.采用HTTP GET的方法: 其基本格式如下: http://webserver/specifiedDirectory/timeservice.asmx/GetTime?ShowSeconds=TRUE 2.采用HTTP POST的方法: 实际上就是通过HTML的FORM格式来发送请求,其主要的代码如下 <form METHOD="POST" ACTION=" http://webserver/specifiedDirectory/timeservice.asmx/GetTime"> <p>是否显示秒?</p> <blockquote> <p> <input TYPE="RADIO" NAME="ShowSeconds" VALUE="True" CHECKED> True <input TYPE="RADIO" NAME="ShowSeconds" VALUE="False">False<br> </p> </blockquote> <input TYPE="SUBMIT" VALUE="Submit Form"> <input TYPE="RESET" VALUE="Reset Form"> </form> 3.采用SOAP的方法: 服务器接受通过HTTP POST请求发送的数据包,数据包以一个SOAP包的格式保存。注意这里的SOAP包实际上是一个XML文档,SOAP包中包含了函数的名称和相关的参数,当SOAP包达到服务器的时候,APS.NET可以识别这个SOAP包,然后提取包中所包含的方法和相应的参数并创建这个对象,然后执行对该方法的调用。并把结果以XML文档的形式返回到客户端。这里的关键是合成一个有效的SOAP包,并对返回的XML文档进行有效的信息提取。 这里我们可以用如下的VB代码来实现基于SOAP的客户端。 Private Const QuoteTemplate = "<?xml version=""1.0""?> <Envelope xmlns = ""http://schemas.xmlsoap.org/soap/envelope/"" > <Body> <GetTime xmlns:xsi=""http://www.w3.org/1999/XMLSchema-instance"" xmlns=""http://tempuri.org/main.xsd""> <ShowSeconds>true</ShowSeconds> </GetTime> </Body> </Envelope>" Private Sub btnGetTime_Click() ' 建立DOM对象 Dim parser As New DOMDocument ' 装载XML文档模板 parser.loadXML (QuoteTemplate) ' 设置参数ShowSeconds的值 If (Check1.Value = 0) Then parser.selectSingleNode("/Envelope/Body/GetTime/ShowSeconds").Text = "false" Else parser.selectSingleNode("/Envelope/Body/GetTime/ShowSeconds").Text = "true" End If ' 把XML文档的内容放到EDIT控件txtSend中 txtSent.Text = parser.xml ' 使用Microsoft Internet transfer 控件执行HTPP POST操作 Inet1.Execute txtURL.Text, "POST", parser.xml, "Content-Type: text/xml" + vbCr + vbLf +"SOAPAction: http://tempuri.org/GetTime" End Sub ' 控件Inetl1的消息触发处理 Private Sub Inet1_StateChanged(ByVal State As Integer) If (State = 12) Then ' 读取从服务器端返回的数据 Dim bar As String bar = Inet1.GetChunk(4096) ' 把服务器端返回的数据存放到EDIT控件txtReceived中 txtReceived.Text = bar ' 把返回的字符串存放到XML文档中 Dim DocIn As New DOMDocument DocIn.loadXML (bar) ' 利用DOM模型得到元素price的值 Dim Price As IXMLDOMNode Set Price = DocIn.selectSingleNode("SOAP:Envelope/SOAP:Body/GetStockQuoteResponse/price") If (Price Is Nothing) Then txtPrice = "(error)" Else txtPrice = Price.Text End If End If End Sub 4.通过智能的SOAP代理进行操作 这种方法需要使用Visual Studio.NET开发工具,程序通过SDL文件读取对Web服务的描述并产生一个Proxy(注意这个Proxy可以使用一定的工具自动产生),该Proxy继承了基类Web.Services.Protocols.SoapClientProtocol。Proxy类包含了一个属性称为Path,这个属性指出服务器的URL地址,它包含一个从SDL文件中得到的缺省的值,客户端通过方法Invoke来激活Proxy中的方法(比如A),该方法(A)然后创建一个SOAP包,包中包含了方法的名称和参数,然后通过HTTP协议把它发送给服务器。当SOAP响应包从服务器中返回的时候,基类就对返回的值进行解析并把它返回给Proxy,再由Proxy返回给客户。然后你可以使用具体的语言来操作这个Proxy,当前支持的语言有Visual Baisc.NET,C#和JavaScript,但不支持C++。 下面是用VB.NET编写的和Proxy进行交互的代码: Public Class Form1 Inherits System.WinForms.Form ' 当点击按纽GetTimeSynch时的处理(同步处理) Protected Sub btnGetTimeSynch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) ' 产生一个新的Proxy类的对象实例 Dim ThisTimeServiceProxy As New TimeService() ' 设置由用户指定的属性Path的值 ThisTimeServiceProxy.Path = TextBox2.Text ' 实际调用方法,并把结果返回到文本框中 ' CheckBox1的值表示方法GetTime的参数值 TextBox1.Text = ThisTimeServiceProxy.GetTime(CheckBox1.Checked) End Sub Dim AsyncTimeServiceProxy As TimeService Dim AsyncResult As IAsyncResult ' 当点击按纽GetTimeASynch时的处理(异步处理) Protected Sub btn_BeginGetTimeAsync_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) AsyncTimeServiceProxy = New TimeService AsyncResult = AsyncTimeServiceProxy.BeginGetTime(CheckBox1.Checked, Nothing, Nothing) End Sub ' 检查当前的操作是否结束 Protected Sub btnPollForComplete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) If (AsyncResult.IsCompleted) Then MsgBox("Complete") Else MsgBox("Not Complete") End If End Sub ' 得到采用异步方法调用所得到的结果 Protected Sub btnEndGetTimeAsync_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) TextBox1.text = AsyncTimeServiceProxy.EndGetTime(AsyncResult) AsyncTimeServiceProxy = Nothing End Sub End Class 我们知道,基于Internet的计算环境要比基于桌面的环境要复杂的多,而且交互时间也要长的多,比如对一个最简单的Web服务的调用可能就要花费5到10秒甚至更长的时间,而在这个时候你不能指望用户愿意傻乎乎的一直等在那里。换句话说,因为桌面交互时间非常短,我们可以采用同步调用的方法来调用组件的一个方法,但是在基于Internet计算的时候,我们必须采用异步调用的方法。 上面的VB.NET代码中也演示了异步调用的方法,实际上Proxy本身就包含了一个方法称为Beginmethodname,就我们具体例子而言就是BeginGetTime,调用这个方法以后,程序会立刻返回,然后可以做其他想做的事情,等你需要看结果的时候,你需要调用方法Endmethodname,然后得到结果。当然如果在结果没有返回前你调用方法Endmethodname的话,程序就会阻塞直到从服务器中得到结果。为了防止整个界面处于阻塞状态,实际实现的时候,你可以在一个工作者线程中调用Endmethodname方法。 五、结论 实际上,几乎所有的编程人员都希望编写能在Internet上进行交互的程序,而不管他们的运行平台到底是什么。而通过使用HTTP和XML技术,我们基本上就能够达到这个功能。客户端只要把数据以XML的形式进行打包,以HTTP形式进行传送,服务器端就能够进行处理,而在服务器端,它通过创建一个SDL文件来描述服务对象所能够提供的方法和参数,这样客户端就可以正确的对服务对象进行调用。同时,一个Proxy的生成器提供了一个到达Web服务的函数集,使客户端程序编写的工作变的更加简单。 需要说明的是,微软即将推出的Visual Studio.NET这套开发工具提供了开发Web服务的集成环境,而这种Web服务的体系结构同时也对Web应用提供了安全级别,这些工具使开发人员能够快速有效的构建基于Internet的应用系统。 |
延伸阅读
文章来源于领测软件测试网 https://www.ltesting.net/