转帖地址:http://blog.csdn.net/cnming/archive/2008/12/20/3561125.aspx
不知道有多少人有遇到跟我一样的问题,就是Web Service的资料回传量太大了,如果都是走区域网路的话,除非量很大,不然还感觉不太出来,可是,如果是透过ADSL的频宽的话,那就很惊人了,以 30MB的资料量来说,透过2M/512K的网路上传,加上网路品质的损耗以65%来看,也大概要花738秒左右来上传. (30*1024)/((512*0.65)/8)=738.46. 这样的时间,看起来很吓人,何况在上传满载时,下载的频宽可能剩40%不到,如果今天能压缩这资料量,让资料量剩25%左右,那上传时间就只要185秒左右,这样的差异就很大了,如果今天我们採用的是n-tier架构,Client是透过Web Service在要资料,那要怎麽去压缩这传输过程中的资料? 这时就可以拿GZipStream出来用了.
起初,GZipStream只是被我拿来当档桉压缩用的,刚好这段时间遇到公司ERP系统效能调校,突然产生了一个想法,如果我能拿它来压缩资料,那传输的资料量不就小很多,尤其是XML的文字档,效果一定更明显,就在这个念头下,着手开始写了测试的程式,在这裡,我做了两种不同的做法,一个是纯压缩,不Serialize,另一种做法是压缩与Serialize,而这两种做法,是会产生些微的资料量差异.
在提到程式內容之前,先說明這做法所造成的資料量差異,詳見下表 :
正常資料量 | 壓縮後 | 壓縮+Serialize |
18,368,067 Bytes | 4,755,570 Bytes | 4,445,278 Bytes |
17,937.57 Kb | 4,644.11 Kb | 4,341.09 Kb |
接下来,就是程式码部份,分两个部份说明:
1 .纯压缩 :
WebService Side :
using System.IO;
using System.IO.Compression;
[WebMethod]
public byte[] getZipData()
{
DataSet ds = LoadData().Copy();
MemoryStream unMS = new MemoryStream();
ds.WriteXml(unMS);
byte[] bytes = unMS.ToArray();
int lenbyte = bytes.Length;
MemoryStream compMs = new MemoryStream();
GZipStream compStream = new GZipStream(compMs, CompressionMode.Compress, true);
compStream.Write(bytes, 0, lenbyte);
compStream.Close();
unMS.Close();
compMs.Close();
byte[] zipData = compMs.ToArray();
return zipData;
}
private DataSet LoadData()
{//产生测试资料用
DataSet ds = new DataSet();
DataTable dt = new DataTable("Test");
dt.Columns.Add("ProID",typeof(int));
dt.Columns.Add("ProName", typeof(string));
dt.Columns.Add("CreateTime", typeof(DateTime));
dt.Columns["ProID"].AutoIncrement = true;
for (int i = 0; i < 100000; i++)
{
DataRow dr = dt.NewRow();
dr["ProName"] = Guid.NewGuid().ToString();
dr["CreateTime"] = DateTime.Now.ToString();
dt.Rows.Add(dr);
}
ds.Tables.Add(dt);
ds.AcceptChanges();
return ds;
}
Client Side :
using System.IO.Compression;
using System.IO;
private void btn_ZipGet_Click(object sender, EventArgs e)
{
try
{
WS.Service1 wss = new WSZipDemo.WS.Service1();//WebReference
byte[] da = wss.getZipData();
MemoryStream input = new MemoryStream();
input.Write(da, 0, da.Length);
input.Position = 0;
GZipStream gzip = new GZipStream(input, CompressionMode.Decompress, true);
MemoryStream output = new MemoryStream();
byte[] buff = new byte[4096];
int read = -1;
read = gzip.Read(buff, 0, buff.Length);
while (read > 0)
{
output.Write(buff, 0, read);
read = gzip.Read(buff, 0, buff.Length);
}
gzip.Close();
byte[] rebytes = output.ToArray();
output.Close();
input.Close();
MemoryStream ms = new MemoryStream(rebytes);
DataSet ds = new DataSet();
ds.ReadXml(ms);
dataGridView1.DataSource = ds.Tables[0];
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
2. 压缩+Serialize
Web Service Side :
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
[WebMethod]
public byte[] getZipData()
{
DataSet ds = LoadData().Copy();
ds.RemotingFormat = SerializationFormat.Binary;
BinaryFormatter ser = new BinaryFormatter();
MemoryStream unMS = new MemoryStream();
ser.Serialize(unMS, ds);
byte[] bytes = unMS.ToArray();
int lenbyte = bytes.Length;
MemoryStream compMs = new MemoryStream();
GZipStream compStream = new GZipStream(compMs, CompressionMode.Compress, true);
compStream.Write(bytes, 0, lenbyte);
compStream.Close();
unMS.Close();
compMs.Close();
byte[] zipData = compMs.ToArray();
return zipData;
}
private DataSet LoadData()
{//产生测试资料用
DataSet ds = new DataSet();
DataTable dt = new DataTable("Test");
dt.Columns.Add("ProID",typeof(int));
dt.Columns.Add("ProName", typeof(string));
dt.Columns.Add("CreateTime", typeof(DateTime));
dt.Columns["ProID"].AutoIncrement = true;
for (int i = 0; i < 100000; i++)
{
DataRow dr = dt.NewRow();
dr["ProName"] = Guid.NewGuid().ToString();
dr["CreateTime"] = DateTime.Now.ToString();
dt.Rows.Add(dr);
}
ds.Tables.Add(dt);
ds.AcceptChanges();
return ds;
}
Client Side :
using System.IO.Compression;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
private void btn_ZipGet_Click(object sender, EventArgs e)
{
try
{
WS.Service1 wss = new WSZipDemo.WS.Service1();//WebReference
byte[] da = wss.getZipData();
MemoryStream input = new MemoryStream();
input.Write(da, 0, da.Length);
input.Position = 0;
GZipStream gzip = new GZipStream(input, CompressionMode.Decompress, true);
MemoryStream output = new MemoryStream();
byte[] buff = new byte[4096];
int read = -1;
read = gzip.Read(buff, 0, buff.Length);
while (read > 0)
{
output.Write(buff, 0, read);
read = gzip.Read(buff, 0, buff.Length);
}
gzip.Close();
byte[] rebytes = output.ToArray();
output.Close();
input.Close();
MemoryStream ms = new MemoryStream(rebytes);
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(ms);
DataSet ds = (DataSet)obj;
dataGridView1.DataSource = ds.Tables[0];
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
这两种做法只是在部份的程式码不一样,但其它大多相同,如果是方法一,在WebService压缩后Return,Client收到资料后,解压缩即可,方法二则多了Serialize这部份,所以在WebService这边,先Serialize,再压缩,Client端收到后,先解压缩再 Deserialize. 不过,这个压缩后的量,是很让人满意,如果在频宽有限,又需要传输大量资料时,这个方法可以考虑看看. 因为这个是.Net 2.0以后才有的东西,公司现在的ERP还在.Net 1.0,所以.........残念~
2008/04/29 补充 :
最近有网友看到这篇文章,有些问题问我朋友,而朋友再来转问我这个问题(真巧,网友问我朋友,我朋友再来问我),而提出的问题是,压缩跟解压是否很耗用CPU的效能? 答桉是,不可能不用到CPU的效能,但它也不致于到"很耗用"的地步. 而这耗用率只能视设备状况来判断,以我的环境来说,Client端的电脑是P4 3GHT, 上面的这个例子跑的时候有个瞬间最高47%,不到一秒的时间,如果是未压缩版的,瞬间最高为37%左右,所以大概多个10%吧.
或许有人还是有疑虑怎麽可以增加CPU的Loading呢,这样就不好了,这时我们就要换个角度来思考这个问题了,"效能成本"所在为何,我们的Bottleneck在那.CPU在科技的进步下,双核四核的都推出了,CPU的效能是快速的在倍增中,而我们的网路呢? 绝大多数区域还在100MB,部份Server与Server是1G在连,而ADSL呢? 10M/1M或者是只能2M/512K,因为申请不到更快的频宽,如果要更快,每月的费用就更高, 注意囉,上传是1MB或512K哦,而且是多人共用的,所以就目前国内的网路环境来看,ADSL要到100M/100M,似乎还要很长的一段时间,而这段时间的CPU,也不知道已经成长到几核心了,两者之间怎麽取捨,就看大家的看法囉.
2008/5/30 补充
因为一些朋友对这个方式感到有兴趣,也在网路上找了一些文章,可是却又发现了一些疑问,到底这个压缩技术能用在什麽样的情况下. 其中一位朋友传给了我一个网址.Net DataTable 大量资料压缩加密实测, 也是说明用压缩的方式来减少流量,一些测试结果也很详尽,有兴趣的人建议参考,只是朋友在他的文章开头的地方看到 [开发 Web 或分散式系统],这是否代表Web网站也可以用? 这个答桉当然是没用的,试想,我们在Web Server压缩,Client端用IE或Firefox怎麽解压缩,网页是无法用Gzipstream的方式来解压缩来减少Web Server到Client的资料量,能压一定要能解才有用,所以就不用再去列出那些可以及那些不行了,毕竟这还关係的架构上的问题,所以应用的关键就是"能压要能解才能用".
另一个问题就是选择性的使用,压缩的动作势必用到系统的效能,如果全面性100%的採用压缩,当使用者多,或操作频率高时,资料量大,系统效能也会变的更加吃重,所以必需挑选几个关键的传输来压缩即可,不用连1K不到的资料量也在压缩,除非Server很勐的,那就另当别论. 依我的情况,我会挑选几个使用者的查询作业来压,依目前手头上的分析资料来看,有个查询作业使用频率较高,资料量也是惊人,最高的资料量就一次高达 33MB,平均起来跟其它作业比较,这作业的资料量佔了不小的比率,光这作业一天平均传输量650MB,压缩后只剩162.5MB在传,所以只压缩这个作业的查询动作,就可明显的改善ADSL的频宽瓶颈. 所以要视情况去选择要压缩的作业.