用 visual foxpro 也可以编写“迅雷下载”,你信吗?
“迅雷下载”的核心技术就是“断点续传”,“多线程”在 vfp
中实现起来比较麻烦(本人还没来得及深入研究),暂不讨论。再加之“迅雷下载”是一个优秀的免费软件,我可不想真的用 vfp
写一个“网络大象”与之较量,只是想把实现方法介绍给大家,仅此而已。好了,言归正传吧 ......
首先要说的就是拨号连接。这可谓是老生常谈了,拨号的方法很多,通过测试我认为下面两个值得一用:
① rasapi32.dll 的 rasdial 函数
如果你需要指定电话号码、用户名及用户口令进行特定拨号的话就需要使用此函数,这多用于对内部服务器拨号。函数原型如下:
declare integer rasdial in rasapi32.dll ;
string lprasdialextensions,; && 在win9x下无用 null
string lpszphonebook,; && 电话簿 在win9x下无用 null
string @lprasdialparams,; && 拨号参数指针
integer dwnotifiertype,; && 消息通知方式 0
integer lpvnotifier,; && 消息处理事件 0
integer @lphrasconn && 返回成功连接的连接句柄 调用前需先置null
调用方法:
m.lprasdialparams = ;
dectoascii(1052,4)+; && dwsize dectoascii(1052,4)
表示将1052转换成低位在前,高位在后的4个ascii字符
replicate(chr(0),256+1)+; && entryname(256) - 拨号连接名字或置为空
stuffc(replicate(chr(0),128+1),1,len(m.phonenumber),m.phonenumber)+;
&& phonenumber(128) 电话号码
replicate(chr(0),128+1)+; && callbacknumber (128) 回叫号码,置空
stuffc(replicate(chr(0),256+1),1,len(m.username),m.username)+;
&& username(256) 用户名
stuffc(replicate(chr(0),256+1),1,len(m.password),m.password)+;
&& password(256) 口令
replicate(chr(0),15+1+3) && domain (15) 域名(自建服务器用)
m.lphrasconn = 0 && 远程访问连接句柄,初值为0
rasdial(null,null,@m.lprasdialparams,0,0,@m.lphrasconn)
值得一提的是“拨号连接名字”,要么准确设置,要么为空。有趣的的是如果“拨号连接名字”设置为空时,拨通后在任务栏中不会显示连接图标(我只测试了xp,其他系统是否如此不得而知)。
② wininet.dll 的 internetdial 函数
如果你对拨号没有特殊指定的话,建议使用此函数拨号。函数原型如下:
declare integer internetdial in wininet.dll;
integer hwndparent,; && 父窗口(表单)句柄或为空
string lpszconnectoid,; && 所使用的拨号连接名字(可以为空)
integer dwflags,; && 连接标志
integer @lpdwconnection,; && 成功连接后的连接号(断开连接时需要此号)
integer dwreserved && 系统保留,必须为零
*-- dwflags - 可取如下各值
#define internet_autodial_force_online 0x0001 && 手动拨号,不在线时提示断开
#define internet_autodial_force_unattended 0x0002 && 自动拨号
#define internet_dial_unattended 0x8000 && 自动拨号
#define internet_dial_force_prompt 0x2000 && 手动拨号
#define internet_dial_show_offline 0x4000 &&
手动拨号(将“取消”按钮替换为“脱机工作”按钮)
调用方法:
m.lpdwconnection = 0
internetdial(0,null,internet_autodial_force_online,@m.lpdwconnection,0)
还有一个问题就是如何断开当前拨号连接。如果当前拨号连接是你的程序建立的,断开比较容易,因为你知道连接句柄。若运行你的程序前拨号连接已经接通,要断开该怎么办呢?其实也很简单,用下面的方法可以断开所有的拨号连接,无论其是以何方式及由谁创建的!
首先,你要取得所有活动的 ras 连接,这要用 rasapi32.dll 的 rasenumconnections 函数
declare integer rasenumconnections in rasapi32.dll ;
string @ lprasconn,; && 缓冲区指针。它是一个 rasconn 结构的数组
integer @ lpcb,; && 缓冲区大小
integer @ lpcconnections && 实际连接数
调用方法:
m.lprasconn = ;
chr(0x9c)+chr(0x01)+chr(0)+chr(0)+; && dwsize - rasconn 结构长度:
412字节
space(4)+; && hrasconn - ras 连接句柄
space(256+1)+; && szentryname
space(16+1)+; && szdevicetype
space(128+1+1) && szdevicename
m.lpcb = 256 * 412 && 假设有256个活动的 ras 连接。其实不会有这么多,只是为了保险。
m.lprasconn = replicate(m.lprasconn,256) && 分配内存
m.lpcconnections = 0 && 初始化连接总数
rasenumconnections(@m.lprasconn,@m.lpcb,@m.lpcconnections)
然后根据变量 m.lpcconnections (实际连接数) 值判断是否有已连通的拨号连接,确定后用
rasapi32.dll 的 rashangup 函数来断开指定的拨号连接。例如,第一个连接的句柄为
substr(m.lprasconn,5,4),第二个为substr(m.lprasconn,412+5,4)...等等,你必须将其转换为十进制数(转换方法请看示例)后赋变量
m.hrasconn 。
declare integer rashangup in rasapi32.dll ;
integer hrasconn && 要终止的 ras 连接句柄
*-- 象下面这样断开指定的连接(通常是第一个连接)
rashangup(m.hrasconn)
好了,有关拨号连接的问题已经解决了。下面该切入正题啦 - 如何实现断点续传!这部分有些难理解,但实现起来也很容易....
首先,你要和 internet 建立连接,这要用到 wininet.dll 的两个函数 internetopen 和
internetconnect。如果要自动处理“脱机工作”状态,还需要用到 internetsetoption 函数。
① wininet.dll 的 internetopen 函数
该函数是取得与 internet 的会话句柄。函数原型如下:
declare integer internetopen in wininet.dll;
string lpszagent,; && 描述
integer dwaccesstype,; && 访问类型
string lpszproxyname,; && 代理服务器名字
string lpszproxybypass,; && 主机名或 ip 地址
integer dwflags && 标志字
其中 lpszagent 可以为任意字符串,甚至是null。dwaccesstype=0 表示从注册表取得访问类型
调用方法:
m.hinternet = internetopen("下载测试",0,null,0,0)
得到 internet 会话句柄后就可以连接到 internet 服务器了。下面只讨论如何连接到 http,因为它最常用。
② wininet.dll 的 internetconnect 函数
取得 http 的连接句柄要用到 internetconnect 函数,原型如下:
declare integer internetconnect in wininet.dll;
integer hinternet,; && internet 会话句柄
string lpszservername,; && 服务器名如 www.microsoft.com
(或服务器ip如:181.231.127.233)
integer nserverport,; && 服务器端口 0-使用 dwservice 指定的服务器端口
string lpszusername,; && 用户名
string lpszpassword,; && 口令
integer dwservice,; && 服务器类型 1-ftp 服务器 2-gopher 服务器 3-http 服务器
integer dwflags,; && 特殊标志
integer dwcontext
调用方法:
m.hconnect =
internetconnect(m.hinternet,'www.microsoft.com',0,null,null,3,0,0)
③ wininet.dll 的 internetsetoption 函数
如果系统正处于“脱机工作”状态,则文件下载将会失败。要切换到联机工作模式就要用到 internetsetoption 函数。
declare integer internetsetoption in wininet.dll;
integer hinternet,; && internet 会话句柄 可设置为零,表示“全局 internet 连接”
integer dwoption,; && 设置类型 50 - 设置连机状态
string lpbuffer,; && 需设置选项
integer dwbufferlength && lpbuffer 长度
调用方法:
m.lpbuffer = chr(1)+chr(0)+chr(0)+chr(0)+; && dwconnectedstate
0x01 - 联机;0x10 - 脱机
replicate(chr(0),4) && dwflags 0x00 - 联机;0x01 - 脱机
internetsetoption(0,50,m.lpbuffer,len(m.lpbuffer))
至此,我们已经连接到了 http
服务器,接下来是判断此服务器是否支持“断点续传”。要完成此操作需要用到三个函数:httpopenrequest、httpsendrequest
和 httpqueryinfo。
① 先用 httpopenrequest 函数获取向 http 服务器发送请求的句柄,该函数原型如下:
declare integer httpopenrequest in wininet.dll;
integer hconnect,; && http 连接句柄
string lpszverb,; && http 协议命令 若为空则函数默认为"get - 表示下载文件"
string lpszobjectname,; && 远程文件名 如:“\dir\xxx.zip”
string lpszversion,; && http 协议版本,若为空则函数默认为http/1.0
string lpszreferer,; && 为 url 指定文档地址
string lpszaccepttypes,; && 文件的打开类型
integer dwflags,; && 标志
integer dwcontext
调用方法:
m.hrequest =
httpopenrequest(m.hconnect,"get","\dir\xxx.zip","http/1.1",null,null,0x4000000,0)
② 有了向 http
服务器发送请求的句柄就可以发送具体请求了。假设我们上次文件已经下载了33字节,现在我们请求服务器从第34个字节开始给发送,这要用
httpsendrequest 函数来实现。
declare integer httpsendrequest in wininet.dll;
integer hrequest,; && http 下载请求句柄
string lpszheaders,; && 附加请求头(可为空) 从第10字节开始下载 range:bytes=10-
integer dwheaderslength,; && 附加请求头长度
integer lpoptional,; && 附加数据缓冲区,可为空
integer dwoptionallength && 附加数据缓冲区长度
调用方法:
m.lpszheaders = 'range:bytes=34-' && 请求服务器从文件的第34字节开始发送数据
httpsendrequest(m.hrequest,m.lpszheaders,len(m.lpszheaders),0,0)
③ 接下来从 http 服务器读取信息以判断是否接受我们的请求 - “断点续传”
declare integer httpqueryinfo in wininet.dll;
integer hrequest,; && http 下载请求句柄
integer dwinfolevel,; && 读取标志
string @ lpvbuffer,; && 数据传递缓冲区
integer @ dwbufferlength,; && 缓冲区长度
integer @ lpdwindex && 返回的参数个数 ?(这个参数我还没搞明白~-~)
调用方法:
m.bufquery = space(32)
m.dwlengthbufquery = len(m.bufquery)
m.lpdwindex = 0
httpqueryinfo(m.hrequest,http_query_status_code,@m.bufquery,@m.dwlengthbufquery,@m.lpdwindex)
如果 substr(m.bufquery,1,m.dwlengthbufquery) 的内容是
'206',值得庆幸,支持“断点续传”,接下来你可以直接调用 internetreadfile
函数从服务器上读取数据了!数据回传成功后用 writefile 函数将其写入本地文件就行啦(^_^)。
下载过程应该启动一个“线程”来进行,这样可以随时中断下载。若用循环控制或是申请一次下载整个文件那我们就无法对下载实施控制了,可在vfp中启动一个“线程”又比较麻烦,因此偷懒的办法是将此工作交给时间控件完成,但效果不太好(将就点吧)。
在时间控件的 timer 事件中可以放入类似下面的代码:
this.enabled = .f. && 禁止本身,等待本事件代码执行完毕
if 用户请求中断下载 .or. 已下载完
释放句柄 m.hrequest、m.hconnect、m.hinternet
关闭本地文件
return
endif
*-- 假定每次下载 1kb
m.dwfilesize = 1024
*-- 分配缓冲区
m.buffer = space(m.dwfilesize)
m.dwbytesread = 0
*-- 将文件读入缓冲区
internetreadfile(m.hrequest,@m.buffer,m.dwfilesize,@m.dwbytesread)
写入本地文件
累计已下载长度
this.enabled = .t. && 激活本身,允许再次执行
若服务器不支持“断点续传”,那你只有将以前下载的不完整文件清空后从头再来啦(~_~)。
另外,你还可以通过设置不同的 dwinfolevel 值来获得其它服务器信息。可取值如下:
http_query_status_code 19 && 状态代码
http_query_status_text 20 && 状态文本
http_query_content_type 1 && 类型
http_query_content_length 5 && 内容长度
http_query_content_range 53 && 范围
http_query_date 9 && 日期
http_query_last_modified 11 && 最后修订
http_query_version 18 && 协议
http_query_raw_headers 21 && 获取 http 信息 - 分隔符为 0
http_query_raw_headers_crlf 22 && 获取 http 信息 - 分隔符为“回车换行符”
http_query_server 37 && 服务器
http_query_rest_method 45 && http协议命令
http_query_etag 54 && etag
注意:设置不同的 dwinfolevel 值一定要将字符串 m.bufquery 设的足够大。一般 m.bufquery =
space(2*1024)就差不多了。
还有一点需要说明,如果服务器不支持“断点续传”,需要重新向服务器提出请求必须先释放前面的 http 请求句柄,然后重新用
httpopenrequest 获取 http 请求句柄,然后用 httpsendrequest
发送新的操作请求,就像下面这样:
internetclosehandle(m.hrequest)
m.hrequest =
"get","\dir\xxx.zip","http/1.1",null,null,0x4000000,0)
httpsendrequest(m.hrequest,m.lpszheaders,len(m.lpszheaders),0,0)
下载完成后别忘了用 internetclosehandle 释放前面所有的 internet
句柄。好了,先到这里吧,实在是懒得写东西。我会尽快将示例整理出来,望斑竹先将此贴“固顶”几天以便上传示例。
下面是其它相关的 函数原型。
*-- 外部函数 - 释放 internet 句柄 返回值 1-成功,0-失败,具体出错原因可通过调用
getlasterror 得到。
declare integer internetclosehandle in wininet.dll ;
integer hinternet && 需释放的句柄
*-- 外部函数 - 从远程文件中读取数据 返回值 成功,返回1。失败,返回 0,可调用
internetgetlastresponseinfo 获得详细内容
declare integer internetreadfile in wininet.dll ;
integer hfile,; && 远程文件句柄
string @lpbuffer,; && 接收缓冲区地址
integer dwnumberofbytestoread,; && 读取字节数
integer @lpdwnumberofbytesread && 实际读取字节数
*-- 外部函数 - 将数据写入一个文件 返回值 非零表示成功,否则返回零
declare integer writefile in kernel32.dll ;
integer hfile,; && 文件句柄
string @lpbuffer,; && 数据缓冲区地址
integer nnumberofbytestowrite,; && 写入字节数
integer @lpnumberofbyteswritten,; && 实际写入字节数
integer lpoverlapped && 该参数应置为零
文章来源:
非常夏日毕业设计 www.bysjdz.com 毕业设计 毕业论文 论文定做 免费论文 开题报告 文献综述 外文翻译 毕业设计定做
找吧!毕业设计 www.zhaobysj.com 毕业设计 毕业论文 论文定做 免费论文 开题报告 文献综述 外文翻译 毕业设计定做