明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

通過壓縮SOAP改善XML Web service性能

[摘要]壓縮文本是一個可以減少文本內(nèi)容尺寸達(dá)80%的過程。這意味著存儲壓縮的文本將會比存儲沒有壓縮的文本少80%的空間。也意味著在網(wǎng)絡(luò)上傳輸內(nèi)容需要更少的時間,對于使用文本通信的客戶端服務(wù)器應(yīng)用程序來說,將會表現(xiàn)出更高的效率,例如XML Web services。 本文的主要目的就是尋找在客戶端和服...
壓縮文本是一個可以減少文本內(nèi)容尺寸達(dá)80%的過程。這意味著存儲壓縮的文本將會比存儲沒有壓縮的文本少80%的空間。也意味著在網(wǎng)絡(luò)上傳輸內(nèi)容需要更少的時間,對于使用文本通信的客戶端服務(wù)器應(yīng)用程序來說,將會表現(xiàn)出更高的效率,例如XML Web services。

本文的主要目的就是尋找在客戶端和服務(wù)器之間使交換的數(shù)據(jù)尺寸最小化的方法。一些有經(jīng)驗的開發(fā)者會使用高級的技術(shù)來優(yōu)化通過網(wǎng)絡(luò)特別是互聯(lián)網(wǎng)傳送的數(shù)據(jù),這樣的做法在許多分布式系統(tǒng)中都存在瓶頸。解決這個問題的一個方法是獲取更多的帶寬,但這是不現(xiàn)實的。另一個方法是通過壓縮的方法使得被傳輸?shù)臄?shù)據(jù)達(dá)到最小。

當(dāng)內(nèi)容是文本的時候,通過壓縮,它的尺寸可以減少80%。這就意味著在客戶端和服務(wù)器之間帶寬的需求也可以減少類似的百分比。為了壓縮和解壓縮,服務(wù)端和客戶端則占用了CPU的額外資源。但升級服務(wù)器的CPU一般都會比增加帶寬便宜,所以壓縮是提高傳輸效率的最有效的方法。

XML/SOAP在網(wǎng)絡(luò)中

讓我們仔細(xì)看看SOAP在請求或響應(yīng)XML Web service的時候,是什么在網(wǎng)絡(luò)上傳輸。我們創(chuàng)建一個XML Web service,它包含一個 add 方法。這個方法有兩個輸入?yún)?shù)并返回這兩個數(shù)的和:

<WebMethod()> Public Function add(ByVal a As Integer, ByVal b As _Integer) As Integer
add = a + b
End Function

當(dāng) XML Web service 消費端調(diào)用這個方法的時候,它確實發(fā)送了一個SOAP請求到服務(wù)器:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body><add xmlns="http://tempuri.org/"><a>10</a><b>20</b></add>
</soap:Body></soap:Envelope>

服務(wù)端使用一個SOAP響應(yīng)來回應(yīng)這個SOAP請求:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body><addResponse xmlns="http://tempuri.org/">
<addResult>30</addResult></addResponse>
</soap:Body></soap:Envelope>

這是調(diào)用XML Web service的方法后,在網(wǎng)絡(luò)上傳輸?shù)膶嶋H信息。在更復(fù)雜的XML Web service中,SOAP響應(yīng)可能是一個很大的數(shù)據(jù)集。例如,當(dāng)Northwind中的表orders中的內(nèi)容被序列化為XML后,數(shù)據(jù)可能達(dá)到454KB。如果我們創(chuàng)建一個應(yīng)用通過XML Web service來獲取這個數(shù)據(jù)集,那么SOAP響應(yīng)將會包含所有的數(shù)據(jù)。

為了提高效率,在傳輸之前,我們可以壓縮這些文本內(nèi)容。我們怎樣才能做到呢?當(dāng)然是使用SOAP擴(kuò)展!

SOAP 擴(kuò)展

SOAP擴(kuò)展是ASP.NET的Web方法調(diào)用的一個攔截機(jī)制,它能夠在SOAP請求或響應(yīng)被傳輸之前操縱它們。開發(fā)者可以寫一段代碼在這些消息序列化之前和之后執(zhí)行。(SOAP擴(kuò)展提供底層的API來實現(xiàn)各種各樣的應(yīng)用。)

使用SOAP擴(kuò)展,當(dāng)客戶端從XML Web service調(diào)用一個方法的時候,我們能夠減小SOAP信息在網(wǎng)絡(luò)上傳輸?shù)某叽纭TS多時候,SOAP請求要比SOAP響應(yīng)小很多(例如,一個大的數(shù)據(jù)集),因此在我們的例子中,僅對SOAP響應(yīng)進(jìn)行壓縮。就像你在圖1中所看到的,在服務(wù)端,當(dāng)SOAP響應(yīng)被序列化后,它會被壓縮,然后傳輸?shù)骄W(wǎng)絡(luò)上。在客戶端,SOAP信息反序列化之前,為了使反序列化成功,SOAP信息會被解壓縮。




圖 1. SOAP信息在序列化后被壓縮(服務(wù)端),在反序列化前被解壓縮(客戶端)

我們也可以壓縮SOAP請求,但在這個例子中,這樣做的效率增加是不明顯的。

為了壓縮我們的Web service的SOAP響應(yīng),我們需要做兩件事情:

· 在服務(wù)端序列化SOAP響應(yīng)信息后壓縮它。
· 在客戶端反序列化SOAP信息前解壓縮它。

這個工作將由SOAP擴(kuò)展來完成。在下面的段落中,你可以看到所有的客戶端和服務(wù)端的代碼。

首先,這里是一個返回大數(shù)據(jù)集的XML Web service:

Imports System.Web.Services
<WebService(Namespace := "http://tempuri.org/")> _
Public Class Service1
Inherits System.Web.Services.WebService

<WebMethod()> Public Function getorders() As DataSet
Dim OleDbConnection1 = New System.Data.OleDb.OleDbConnection()
OleDbConnection1.ConnectionString = "Provider=SQLOLEDB.1; _
Integrated Security=SSPI;Initial Catalog=Northwind; _
Data Source=.;Workstation ID=T-MNIKIT;"
Dim OleDbCommand1 = New System.Data.OleDb.OleDbCommand()
OleDbCommand1.Connection = OleDbConnection1
OleDbConnection1.Open()
Dim OleDbDataAdapter1 = New System.Data.OleDb.OleDbDataAdapter()
OleDbDataAdapter1.SelectCommand = OleDbCommand1
OleDbCommand1.CommandText = "Select * from orders"
Dim objsampleset As New DataSet()
OleDbDataAdapter1.Fill(objsampleset, "Orders")
OleDbConnection1.Close()
Return objsampleset
End Function

End Class



在客戶端,我們構(gòu)建了一個Windows應(yīng)用程序來調(diào)用上面的XML Web service,獲取那個數(shù)據(jù)集并顯示在DataGrid中:

Public Class Form1
Inherits System.Windows.Forms.Form
'This function invokes the XML Web service, which returns the dataset
'without using compression.
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim ws As New wstest.Service1()
Dim test1 As New ClsTimer()
'Start time counting…
test1.StartTiming()
'Fill datagrid with the dataset
DataGrid1.DataSource = ws.getorders()
test1.StopTiming()
'Stop time counting…
TextBox5.Text = "Total time: " & test1.TotalTime.ToString & "msec"
End Sub

'This function invokes the XML Web service, which returns the dataset
'using compression.
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
Dim ws As New wstest2.Service1()
Dim test1 As New ClsTimer()
'Start time counting…
test1.StartTiming()
'Fill datagrid with dataset
DataGrid1.DataSource = ws.getorders()
test1.StopTiming()
'Stop time counting…
TextBox4.Text = "Total time: " & test1.TotalTime.ToString & "msec"
End Sub

End Class

客戶端調(diào)用了兩個不同的XML Web services, 僅其中的一個使用了SOAP壓縮。下面的Timer類是用來計算調(diào)用時間的:

Public Class ClsTimer
' Simple high resolution timer class
'
' Methods:
' StartTiming reset timer and start timing
' StopTiming stop timer
'
'Properties
' TotalTime Time in milliseconds
'Windows API function declarations
Private Declare Function timeGetTime Lib "winmm" () As Long

'Local variable declarations
Private lngStartTime As Integer
Private lngTotalTime As Integer
Private lngCurTime As Integer

Public ReadOnly Property TotalTime() As String
Get
TotalTime = lngTotalTime
End Get
End Property

Public Sub StartTiming()
lngTotalTime = 0
lngStartTime = timeGetTime()
End Sub

Public Sub StopTiming()
lngCurTime = timeGetTime()
lngTotalTime = (lngCurTime - lngStartTime)
End Sub
End Class

服務(wù)端的SOAP擴(kuò)展

在服務(wù)端,為了減小SOAP響應(yīng)的尺寸,它被壓縮。下面這段告訴你怎么做:

第一步

使用Microsoft Visual Studio .NET, 我們創(chuàng)建一個新的Visual Basic .NET 類庫項目(使用"ServerSoapExtension"作為項目名稱),添加下面的類:

Imports System
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.IO
Imports zipper

Public Class myextension
Inherits SoapExtension
Private networkStream As Stream
Private newStream As Stream

Public Overloads Overrides Function GetInitializer(ByVal _
methodInfo As LogicalMethodInfo, _
ByVal attribute As SoapExtensionAttribute) As Object
Return System.DBNull.Value
End Function

Public Overloads Overrides Function GetInitializer(ByVal _
WebServiceType As Type) As Object
Return System.DBNull.Value
End Function

Public Overrides Sub Initialize(ByVal initializer As Object)
End Sub

Public Overrides Sub ProcessMessage(ByVal message As SoapMessage)
Select Case message.Stage

Case SoapMessageStage.BeforeSerialize

Case SoapMessageStage.AfterSerialize
AfterSerialize(message)

Case SoapMessageStage.BeforeDeserialize
BeforeDeserialize(message)

Case SoapMessageStage.AfterDeserialize

Case Else
Throw New Exception("invalid stage")
End Select
End Sub

' Save the stream representing the SOAP request or SOAP response into a
' local memory buffer.
Public Overrides Function ChainStream(ByVal stream As Stream) As Stream
networkStream = stream
newStream = New MemoryStream()
Return newStream
End Function

' Write the compressed SOAP message out to a file at
'the server's file system..
Public Sub AfterSerialize(ByVal message As SoapMessage)
newStream.Position = 0
Dim fs As New FileStream("c:\temp\server_soap.txt", _
FileMode.Append, FileAccess.Write)
Dim w As New StreamWriter(fs)
w.WriteLine("-----Response at " + DateTime.Now.ToString())
w.Flush()
'Compress stream and save it to a file
Comp(newStream, fs)
w.Close()
newStream.Position = 0
'Compress stream and send it to the wire
Comp(newStream, networkStream)
End Sub

' Write the SOAP request message out to a file at the server's file system.
Public Sub BeforeDeserialize(ByVal message As SoapMessage)
Copy(networkStream, newStream)
Dim fs As New FileStream("c:\temp\server_soap.txt", _
FileMode.Create, FileAccess.Write)
Dim w As New StreamWriter(fs)
w.WriteLine("----- Request at " + DateTime.Now.ToString())
w.Flush()
newStream.Position = 0
Copy(newStream, fs)
w.Close()
newStream.Position = 0
End Sub

Sub Copy(ByVal fromStream As Stream, ByVal toStream As Stream)
Dim reader As New StreamReader(fromStream)
Dim writer As New StreamWriter(toStream)
writer.WriteLine(reader.ReadToEnd())
writer.Flush()
End Sub

Sub Comp(ByVal fromStream As Stream, ByVal toStream As Stream)
Dim reader As New StreamReader(fromStream)
Dim writer As New StreamWriter(toStream)
Dim test1 As String
Dim test2 As String
test1 = reader.ReadToEnd
'String compression using NZIPLIB
test2 = zipper.Class1.Compress(test1)
writer.WriteLine(test2)
writer.Flush()
End Sub

End Class

' Create a SoapExtensionAttribute for the SOAP extension that can be
' applied to an XML Web service method.
<AttributeUsage(AttributeTargets.Method)> _
Public Class myextensionattribute
Inherits SoapExtensionAttribute

Public Overrides ReadOnly Property ExtensionType() As Type
Get
Return GetType(myextension)
End Get
End Property

Public Overrides Property Priority() As Integer
Get
Return 1
End Get
Set(ByVal Value As Integer)
End Set
End Property

End Class



第二步

我們增加ServerSoapExtension.dll程序集作為引用,并且在web.config聲明SOAP擴(kuò)展:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

<system.web>
<webServices>
<soapExtensionTypes>
<add type="ServerSoapExtension.myextension,
ServerSoapExtension" priority="1" group="0"/>
</soapExtensionTypes>
</webServices>

...
</system.web>
</configuration>

就象你在代碼中看到的那樣,我們使用了一個臨時目錄("c:\temp")來捕獲SOAP請求和壓縮過的SOAP響應(yīng)到文本文件("c:\temp\server_soap.txt")中 。

客戶端的SOAP擴(kuò)展

在客戶端,從服務(wù)器來的SOAP響應(yīng)被解壓縮,這樣就可以獲取原始的響應(yīng)內(nèi)容。下面就一步一步告訴你怎么做:

第一步

使用Visual Studio .NET, 我們創(chuàng)建一個新的Visual Basic .NET類庫項目(使用 "ClientSoapExtension"作為項目名稱),并且添加下面的類:

Imports System
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.IO
Imports zipper
Public Class myextension
Inherits SoapExtension

Private networkStream As Stream
Private newStream As Stream

Public Overloads Overrides Function GetInitializer(ByVal _
methodInfo As LogicalMethodInfo, _
ByVal attribute As SoapExtensionAttribute) As Object
Return System.DBNull.Value
End Function

Public Overloads Overrides Function GetInitializer(ByVal _
WebServiceType As Type) As Object
Return System.DBNull.Value
End Function

Public Overrides Sub Initialize(ByVal initializer As Object)
End Sub

Public Overrides Sub ProcessMessage(ByVal message As SoapMessage)
Select Case message.Stage

Case SoapMessageStage.BeforeSerialize

Case SoapMessageStage.AfterSerialize
AfterSerialize(message)

Case SoapMessageStage.BeforeDeserialize
BeforeDeserialize(message)

Case SoapMessageStage.AfterDeserialize

Case Else
Throw New Exception("invalid stage")
End Select
End Sub

' Save the stream representing the SOAP request or SOAP response
' into a local memory buffer.
Public Overrides Function ChainStream(ByVal stream As Stream) _
As Stream
networkStream = stream
newStream = New MemoryStream()
Return newStream
End Function

' Write the SOAP request message out to a file at
' the client's file system.
Public Sub AfterSerialize(ByVal message As SoapMessage)
newStream.Position = 0
Dim fs As New FileStream("c:\temp\client_soap.txt", _
FileMode.Create, FileAccess.Write)
Dim w As New StreamWriter(fs)
w.WriteLine("----- Request at " + DateTime.Now.ToString())
w.Flush()
Copy(newStream, fs)
w.Close()
newStream.Position = 0
Copy(newStream, networkStream)
End Sub

' Write the uncompressed SOAP message out to a file
' at the client's file system..
Public Sub BeforeDeserialize(ByVal message As SoapMessage)
'Decompress the stream from the wire
DeComp(networkStream, newStream)
Dim fs As New FileStream("c:\temp\client_soap.txt", _
FileMode.Append, FileAccess.Write)
Dim w As New StreamWriter(fs)
w.WriteLine("-----Response at " + DateTime.Now.ToString())
w.Flush()
newStream.Position = 0
'Store the uncompressed stream to a file
Copy(newStream, fs)
w.Close()
newStream.Position = 0
End Sub

Sub Copy(ByVal fromStream As Stream, ByVal toStream As Stream)
Dim reader As New StreamReader(fromStream)
Dim writer As New StreamWriter(toStream)
writer.WriteLine(reader.ReadToEnd())
writer.Flush()
End Sub

Sub DeComp(ByVal fromStream As Stream, ByVal toStream As Stream)
Dim reader As New StreamReader(fromStream)
Dim writer As New StreamWriter(toStream)
Dim test1 As String
Dim test2 As String
test1 = reader.ReadToEnd
'String decompression using NZIPLIB
test2 = zipper.Class1.DeCompress(test1)
writer.WriteLine(test2)
writer.Flush()
End Sub

End Class

' Create a SoapExtensionAttribute for the SOAP extension that can be
' applied to an XML Web service method.
<AttributeUsage(AttributeTargets.Method)> _
Public Class myextensionattribute
Inherits SoapExtensionAttribute

Public Overrides ReadOnly Property ExtensionType() As Type
Get
Return GetType(myextension)
End Get
End Property

Public Overrides Property Priority() As Integer
Get
Return 1
End Get
Set(ByVal Value As Integer)
End Set
End Property

End Class

就象你在代碼中看到的那樣,我們使用了一個臨時目錄("c:\temp") 來捕獲SOAP請求和解壓縮的SOAP響應(yīng)到文本文件("c:\temp\client_soap.txt")中。

第二步

我們添加ClientSoapExtension.dll程序集作為引用,并且在我們的應(yīng)用程序的XML Web service引用中聲明SOAP擴(kuò)展:

'-------------------------------------------------------------------------
' <autogenerated>
' This code was generated by a tool.
' Runtime Version: 1.0.3705.209
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' </autogenerated>
'-------------------------------------------------------------------------
Option Strict Off
Option Explicit On

Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization

'
'This source code was auto-generated by Microsoft.VSDesigner,
'Version 1.0.3705.209.
'
Namespace wstest2

'<remarks/>
<System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Web.Services.WebServiceBindingAttribute(Name:="Service1Soap", _
[Namespace]:="http://tempuri.org/")> _
Public Class Service1
Inherits System.Web.Services.Protocols.SoapHttpClientProtocol

'<remarks/>
Public Sub New()
MyBase.New
Me.Url = "http://localhost/CompressionWS/Service1.asmx"
End Sub

'<remarks/>
<System.Web.Services.Protocols.SoapDocumentMethodAttribute _
("http://tempuri.org/getproducts",RequestNamespace:= _
"http://tempuri.org/", ResponseNamespace:="http://tempuri.org/", _
Use:=System.Web.Services.Description.SoapBindingUse.Literal,_
ParameterStyle:=System.Web.Services.Protocols.SoapParameterStyle _
.Wrapped), ClientSoapExtension.myextensionattribute()> _
Public Function getproducts() As System.Data.DataSet
Dim results() As Object = Me.Invoke("getproducts", _
New Object(-1) {})
Return CType(results(0), System.Data.DataSet)
End Function

'<remarks/>
Public Function Begingetproducts(ByVal callback As _
System.AsyncCallback, ByVal asyncState As Object) As System.IAsyncResult _

Return Me.BeginInvoke("getproducts", New Object(-1) {}, _
callback, asyncState)
End Function

'<remarks/>
Public Function Endgetproducts(ByVal asyncResult As _
System.IAsyncResult) As System.Data.DataSet
Dim results() As Object = Me.EndInvoke(asyncResult)
Return CType(results(0), System.Data.DataSet)
End Function
End Class
End Namespace



這里是zipper類的源代碼,它是使用免費軟件NZIPLIB庫實現(xiàn)的:

using System;
using NZlib.GZip;
using NZlib.Compression;
using NZlib.Streams;
using System.IO;
using System.Net;
using System.Runtime.Serialization;
using System.Xml;
namespace zipper
{
public class Class1
{
public static string Compress(string uncompressedString)
{
byte[] bytData = System.Text.Encoding.UTF8.GetBytes(uncompressedString);
MemoryStream ms = new MemoryStream();
Stream s = new DeflaterOutputStream(ms);
s.Write(bytData, 0, bytData.Length);
s.Close();
byte[] compressedData = (byte[])ms.ToArray();
return System.Convert.ToBase64String(compressedData, 0, _
compressedData.Length);
}

public static string DeCompress(string compressedString)
{
string uncompressedString="";
int totalLength = 0;
byte[] bytInput = System.Convert.FromBase64String(compressedString);;
byte[] writeData = new byte[4096];
Stream s2 = new InflaterInputStream(new MemoryStream(bytInput));
while (true)
{
int size = s2.Read(writeData, 0, writeData.Length);
if (size > 0)
{
totalLength += size;
uncompressedString+=System.Text.Encoding.UTF8.GetString(writeData, _
0, size);
}
else
{
break;
}
}
s2.Close();
return uncompressedString;
}
}
}



分析

軟件&硬件

· 客戶方: Intel Pentium III 500 MHz, 512 MB RAM, Windows XP.
· 服務(wù)器方: Intel Pentium III 500 MHz, 512 MB RAM, Windows 2000 Server, Microsoft SQL Server 2000.

在客戶端,一個Windows應(yīng)用程序調(diào)用一個XML Web service。這個XML Web service 返回一個數(shù)據(jù)集并且填充客戶端應(yīng)用程序中的DataGrid。



圖2. 這是一個我們通過使用和不使用SOAP壓縮調(diào)用相同的XML Web Service的樣例程序。這個XML Web service返回一個大的數(shù)據(jù)集。

CPU 使用記錄

就象在圖3中顯示的那樣,沒有使用壓縮的CPU使用時間是 29903 milliseconds.



圖 3. 沒有使用壓縮的CPU使用記錄

在我們的例子中,使用壓縮的CPU使用時間顯示在圖4中, 是15182 milliseconds.



圖 4. 使用壓縮的CPU 使用記錄

正如你看到的,我們在客戶方獲取這個數(shù)據(jù)集的時候,使用壓縮與不使用壓縮少用了近50%的CPU時間,僅僅在CPU加載時有一點影響。當(dāng)客戶端和服務(wù)端交換大的數(shù)據(jù)時,SOAP壓縮能顯著地增加XML Web Services效率。 在Web中,有許多改善效率的解決方案。我們的代碼是獲取最大成果但是最便宜的解決方案。SOAP擴(kuò)展是通過壓縮交換數(shù)據(jù)來改善XML Web Services性能的,這僅僅對CPU加載時間造成了一點點影響。并且值得提醒的是,大家可以使用更強(qiáng)大的和需要更小資源的壓縮算法來獲取更大的效果