Welcome

首页 / 软件开发 / VFP / 用Web Service传送文件(一)

用Web Service传送文件(一)2007-05-08开篇

去年,第一次编写 Web Service 的时候,我就有个疑问:这玩艺儿能不能传送文件!

在实际开发中传写文件是经常性的需求,Visual FoxPro 程序员要在 Internet 上做这件事情,通常是用电子邮件或者是FTP的方式,那么 Web Service 能不能提供一种新方法呢?

当然可以用 Web Service 传送文件,这就是今天我们讨论的主题。

在开始之前,要感谢 将来是我、漫步者和 BOBY 在有关技术上对我的帮助,将这篇文章献给你们!!!

把文件转化为文本

Base64 编码/解码

我在以前的文章中多次提到 Web Service 是依靠 XML 来描述数据的,延伸到传送文件的问题,可以这么想:只要能用 XML 表示被传送的文件,就可以达到用 Web Service 的目标!

计算机里的各种文件,基本上都是以两进制形式存在的;而我们知道 XML 只是最简单的文本信息。那么怎样实现任意文件与文本的相互转化呢?这显然是问题的关键所在!

我们就不饶圈子了,直接给出解决方案:Base64 编码/解码!Base64 编码/解码 规则是在 RFC2045,Multipurpose Internet Mail Extension (MIME) 里定义的。所谓 编码 就是:把两进制数据转换成 Base64(文本信息);所谓 解码 就是把 Base64 (文本信息) 还原为原始数据!

注意过没有:电子邮件附件也多用 Base64 编码/解码!

Base64 是文本,它用64个可见字符表示信息,这64个可见字符是:a-z,A-Z、0-9、+、/、=。

具体实现 Base64 编码/解码 的算法也不是太困难,由于 Visual FoxPro 7 对 Base64 有具体的支持,所以这里我就不谈论 Base64 的算法了!

就请记住一个结论:XML 只能传“可见文本”,我们对任意文件用 Base64 编码转换为文本,于是就可以用XML就能传送了。传送完毕,只需要把 Base64 解码成为两进制数据,保存为文件就行了!

对了,还有一点要说明的是:根据Base64编码的算法,数据转换为 Base64 以后,一般体积要比原先增加1/3!这也许就是使用 Base64的代价了……

在 Visual FoxPro 里实现 Base64 编码/解码

发现 StrConv() 的秘密

Visual FoxPro 里有函数能解决“Base64 编码/解码”的问题吗?没有,你去查遍帮助都可能找不到!也许我的运气好、也许是我的感觉好,为了这个问题我特意测试了 Visual FoxPro 专门用来编码、解码的函数 STRCONV(cExpression, nConversionSetting ),有趣的是 IntelliSense 揭示了参数 nConversionSetting 的秘密!

nConversionSetting=13 时,实现 Base64 编码功能:把两进制数据转换成 Base64(文本信息)!

nConversionSetting=14 时,实现 Base64 解码功能:把 Base64 (文本信息) 还原为原始数据!

不知道为什么,微软没有把这两个参数编入帮助,起码我的帮助没有提到这两个参数,真是暴敛天物!!!

使用 StrConv() 函数

如果要把 C:abc.jpg 转化为 Base64 编码,可以这样实现:

local cBase64 as String
cBase64=Strconv(FiletoStr("c:abc.jpg"),13)

如果要把上述 Base64 编码还原为文件 test.jpg,可以这样实现:

StrtoFile(Strconv(cBase64,14),"c: est.jpg",.t.)

是不是非常容易?就是一层窗户纸,点破就一点儿也不稀奇了!其实,如果您读过 Boe 以前关于制作 Web Service 的文章,加上今天的这么点技巧,“怎么用 Web Service 传送文件” 对您应该是很容易的事情了!

实现 Web Service

传送一张图片

服务器端的代码

这是一个非常简单的程序:接口函数 GetPicture 返回一个承载着图片的 XML 字符串给客户端程序!这里为了简化代码,被返回的图片也是固定的:pic.jpg。

受保护函数 Encode64 能够将制定的文件编码成为 Base64,核心代码就是:

RETURN STRCONV(FILETOSTR(sFileName),13)

下面就是这两个函数,具体见prg1.prg文件!

PROTECTED FUNCTION Encode64(sFileName as string) as String
IF FILE(sFileName)
RETURN STRCONV(FILETOSTR(sFileName),13)
ELSE
RETURN ""
ENDIF
ENDFUNC

FUNCTION GetPicture as String
RETURN this.Encode64(DataBasePath+"pic.jpg")
ENDPROC

客户端调用

客户端调用这个Web Service 也是很容易的事情:我们使用 GetPicture() 接口取得承载图片的 XML 字符串,并还原它为test.jpg,然后把这个 jpg 文件贴在 Visual FoxPro 的主窗体里!

这里用于解码 base64 并保存文件的代码是:

STRTOFILE(STRCONV(mywebs.GetPicture,14),TempPath+"test.jpg",.t.)

完整代码如下,具体见Getpic.prg:

#define TempPath "J:websClientTemp"
LOCAL mywebs as FoxWebService
LOCAL loWS
loWS = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx")
loWS.cWSName = "FoxWebService"
mywebs = loWS.SetupClient("http://BOEWORKS/FoxWebS/FoxWebService.WSDL", "FoxWebService", "FoxWebServiceSoapPort")
STRTOFILE(STRCONV(mywebs.GetPicture,14),TempPath+"test.jpg",.t.)
_screen.Picture=TempPath+"test.jpg"

传送 DBF 文件

引入 Zip 技术

以上我们解决了用 Web Service 传送文件的问题,其实仔细思考一下,您会发现还有一些细节问题需要有交代:

  • 有的被传送文件(数据)很大,传送起来对网络压力很大,而且耗费时间。怎么使数据变小、缩短传送时间、减轻网络压力呢?

  • 如果一次需要传送多个文件该怎么实现?

我的解决方法是:用 Zip 方式压缩被传送文件。

Zip 对 BMP、TXT、DBF 等类型的文件有很高的压缩率,压缩这些文件能使数据量大大减少,对快速传递非常有利,同时也减少了网络上的数据流量!

Zip 还有一个功能就是可以将多了文件包裹在一个Zip文件里,这样就解决了一次传递多个文件的问题。

另外,Zip 还支持压缩口令,有资料提到用十几位长度的中英文混合的压缩口令,一般很难破解!这里我们压缩时加入口令保护,对文件传输安全应该是有好处的!

很可惜,Visual FoxPro 没有直接提供 Zip 功能,一般我们使用第三方的动态链接库或者是 ActiveX 控件来实现 Zip。这里我使用了名为 ZipToolKit.dll 的动态链接库,是我封装的。功能简单,但用在 Web Service 客户端和服务器端应该是可以胜任的(ZipToolKit.dll 是我为了这篇文章特意编写的,所以我不对 ZipToolKit.dll 提供任何技术支持和担保!)。

ZipToolKit.dll 的用法

压缩文件

函数声明:DECLARE Integer ZipFiles IN "ziptoolkit.dll" String,String,integer,String
返回值:0>=0 被成功压缩文件的数量;-1 失败
参数1:被压缩文件列表。用全路径表示文件;支持通佩符;多个文件用分号分割;参数长度不超过255个字符
参数2:压缩后文件名称。
参数3:压缩级别。取值 1-9,取值越大,压缩比率越高,处理越慢。
参数4:压缩口令。空白字符串表示不设口令。

例如:
DECLARE Integer ZipFiles IN "ziptoolkit.dll" as zip String,String,integer,String
?Zip("F:zipabc.bmp;F:zipab.txt","F:zip est.zip",9,"")
clear dlls "zip"

解压缩文件

函数声明:DECLARE Integer UnZipFile IN "ziptoolkit.dll" String,String,String
返回值:0>=0成功解压缩文件的数量;-1失败
参数1:被解压缩文件
参数2:释放路径
参数3:解压缩口令。空白字符串表示不设口令。

例如:
DECLARE Integer UnZipFile IN "ziptoolkit.dll" as unzip String,String,String
?unzip("F:zip est.zip","F:zip 1t","")
clear dlls "unzip"

为什么这里不用 CursorToXML() 处理 DBF 的传送问题

言归正题,让我们使用 Web Service 传送 DBF 文件。如果您是 BOE 的老读者,也许你会有这样的疑问:DBF 不是可以通过 cursortoxml() 函数直接转为 XML 字符串,为什么要另辟蹊径把 DBF 简单的作为文件、用通用的传送文件的方式处理呢?

我想有两点理由。其一,cursortoxml() 函数不支持通用字段数据的转换,也就是说如果一个 Cursor(DBF) 里有通用字段,这个字段将被 cursortoxml() 忽略,说的再明显一点就是,cursortoxml 不支持通用字段类型!遇到这样的问题,我个人以为直接传送 *.dbf 和*.fpt 文件是最简单的方法!

把 DBF 作为文件传递还有另一个原因,当数据量稍微大一点的时侯,用 cursortoxml() 会产生庞大的 XML 字符串(上千条数据,就可能形成几M的 XML),如果采用 Zip 压缩 DBF,有极好的“瘦身”作用!

当然,我们以前介绍的 cursortoxml() 的做法依然有它的好处,并且它依然是传送数据表的主流方法,这是因为在 Internet 的应用时,我们并不提倡让大量数据在网络上传递,只是传送有用的行和列;同时,一般的商务程序处理通用数据的机会并不多;还有使用 cursortoxml() 函数处理表格数据,是非常简单的。所以千万不要因为今天的话题而 “走火入魔” 了!!!

服务器端的代码

这是一个非常简单的程序:接口函数 GetDBF 返回一个承载着 DBF 文件的 XML 字符串给客户端程序(其实被传送的是 Zip 文件的 Base64 编码)!这里为了简化代码,被返回的表文件是固定的:employee.dbf 和它的附属文件 employee.fpt。

受保护函数 ZipEncode64 能够将指定的文件压缩成为 zip文件,再将zip文件编码成为 Base64:

下面就是这两个函数,具体见prg1.prg文件!

PROTECTED FUNCTION ZipEncode64(sFile as string,iLevel as Integer,sPsd as string) as String
LOCAL sTempZipFile,sBase64 as String
sTempZipFile=TempPath+SYS(2015)
DECLARE Integer ZipFiles IN "ziptoolkit.dll" as zip String,String,integer,String
IF zip(sFile,sTempZipFile,iLevel,sPsd)>0
sBase64=this.Encode64(sTempZipFile)
ELSE
sBase64=""
ENDIF
CLEAR DLLS "ZIP"
IF FILE(sTempZipFile)
DELETE FILE &sTempZipFile
ENDIF
RETURN sBase64
ENDFUNC

FUNCTION GetDbf(iZipLevel as Integer,sPsd as string) as String
RETURN this.ZipEncode64(DataBasePath+"employee.dbf;"+DataBasePath+"employee.fpt",iZipLevel,sPsd)
ENDPROC

客户端调用

客户端调用这个Web Service 也是很容易的事情:我们使用 GetDBF() 接口取得承载 Zip 的 XML 字符串,并还原它为test.zip,然后把这个 zip 文件解压缩到临时目录里,就得到了employee 表文件,最后打开并显示 employee!

完整代码如下,具体见GetDBF.prg:

#define TempPath "J:websClientTemp"
#define ToolPath "J:webs ools"
LOCAL mywebs as FoxWebService
LOCAL loWS
loWS = NEWOBJECT("Wsclient",HOME()+"ffc\_webservices.vcx")
loWS.cWSName = "FoxWebService"
mywebs = loWS.SetupClient("http://BOEWORKS/FoxWebS/FoxWebService.WSDL", "FoxWebService", "FoxWebServiceSoapPort")
SET PATH TO ToolPath
IF SELECT("employee")>0
USE IN employee
ENDIF
STRTOFILE(STRCONV(mywebs.GetDbf(9,"BOE 数据网络工作室"),14),TempPath+"test.zip",.t.)
DECLARE Integer UnZipFile IN "ziptoolkit.dll" as unZip String,String,String
IF UnZip(TempPath+"test.zip",TempPath,"BOE 数据网络工作室")>0
USE TempPath+"employee"
BROWSE
ENDIF
CLEAR DLLS "unzip"

并在一起

我还不了解 Web Service 怎么办?

如果,您不了解怎么编写 Web Service 服务端程序,请查看 BOE 的《用Visual FoxPro编写Web Service》;

如果,您不了解怎么编写 Web Service 客户端程序,请查看 BOE 的《Visual FoxPro 7 全新登场--Web Service Client》;

下载的示例怎么使用

与往常一样,基本上编译、发布以后就可以使用了。要注意的就是每一个 prg 文件里都有预定义变量,一般是路径指向,根据你那里情况改变它们就行了!

Web Service 到底有什么用?

如果大家亲身感受过 Web Service,您就会发现调用 Internet 上的 Web Service 就像调用本机的函数那么简单。通过 Web Service,我们可以交互各种数据,包括文件!

也许今天 Foxer 对 Web Service 的疑惑,就像当年对 SQL Server 的疑惑那样。做个有心人吧,也许你永远不会使用这个技术,也许明天你就要使用这门技术,了解它总是好的,这是我的观点!

有网友说我不务实,老是弄一些虚的东西,反正也没几个人看得懂,蒙人罢了。也不知道说的是对、是错,更不知道是从那里抄来的……这种恶意中伤,不知道这些人是什么心态,对此我只想说:兄弟,请给我一片宁静的天空!!!

好了,就到这里!下次再见!