• 【顶】Asp无组件上传进度条解决方案


    一、无组件上传的原理
    我还是一点一点用一个实例来说明的吧,客户端html如下。要浏览上传附件,我们通过<input type="file">元素,但是一定要注意必须设置form的enctype属性为"multipart/form-data":


    <form method="post" action="upload.asp" enctype="multipart/form-data">
    <label>
    <input type="file" name="file1" />
    </label>
    <br />
    <input type="text" name="filename" value="default filename"/>
    <br />
    <input type="submit" value="submit"/>
    <input type="reset" value="reset"/>
    </form>


    在后台asp程序中,以前获取表单提交的ascii 数据,非常的容易。但是如果需要获取上传的文件,就必须使用request对象的binaryread方法来读取。binaryread方法是对当前输入流进行指定字节数的二进制读取,有点需要注意的是,一旦使用binaryread 方法后,再也不能使用request.form 或 request.querystring 集合了。结合request对象的totalbytes属性,可以将所有表单提交的数据全部变成二进制,不过这些数据都是经过编码的。首先让我们来看看这些数据是如何编码的,有无什么规律可循,编段代码,在代码中我们将binaryread读取的二进制转化为文本,输出出来,在后台的upload.asp 中(注意该示例不要上传大文件,否则可能会造成浏览器死掉):
    <%
    dim bidata, postdata
    size = request.totalbytes
    bidata = request.binaryread(size)
    postdata = binarytostring(bidata,size)
    response.write "<pre>" & postdata & "</pre>" 使用pre,原样输出格式
    借助recordset将二进制流转化成文本
    function binarytostring(bidata,size)
    const adlongvarchar = 201
    set rs = createobject("adodb.recordset")
    rs.fields.append "mbinary", adlongvarchar, size
    rs.open
    rs.addnew
    rs("mbinary").appendchunk(bidata)
    rs.update
    binarytostring = rs("mbinary").value
    rs.close
    end function
    %>


    简单起见,上传一个最简单的文本文件(g:\homepage.txt,内容为"宝玉:http://www.webuc.net";)来试验一下,文本框filename中保留默认值"default filename",提交看看输出结果:

    -----------------------------7d429871607fe
    content-disposition: form-data; name="file1"; filename="g:\homepage.txt"
    content-type: text/plain
    宝玉:http://www.webuc.net
    -----------------------------7d429871607fe
    content-disposition: form-data; name="filename"
    default filename
    -----------------------------7d429871607fe--

    可以看出来对于表单中的项目,是用过"-----------------------------7d429871607fe"这样的边界来分隔成一块一块的,每一块的开始都有一些描述信息,例如:content-disposition: form-data; name="filename",在描述信息中,通过name="filename"可以知道表单项的name。如果有 filename="g:\homepage.txt"这样的内容,说明是一个上传的文件,如果是一个上传的文件,那么枋鲂畔⒒岫嘁恍蠧ontent- type: text/plain来描述文件的content-type。描述信息和主体信息之间是通过换行来分隔的。

    嗯,基本上清晰了,根据这个规律我们就知道该怎么来分离数据,再对分离的数据进行处理了,不过差点忽略一个问题,就是边界值(上例中的"-----------------------------7d429871607fe")是怎么知道的?每次上传这个边界值是不一样的,还好还好 asp中可以通过request.servervariables( "http_content_type")来获之,例如上例中http_content_type内容为:"multipart/form-data; boundary=---------------------------7d429871607fe",有了这个,我们不仅可以判断客户端的form 中有无使用enctype="multipart/form-data"(如果没有使用,那么下面就没必要执行啦),还可以获取边界值 boundary=---------------------------7d429871607fe。(注意:这里获取的边界值比上面的边界值开头要少"--",最好补充上。)

    至于如何分析数据的过程我就不多赘述了,无非就是借助instr,mid等这样的函数来分离出来我们想要的数据。

    二、分块上传,记录进度
    要实时反映进度条,实质就是要实时知道当前服务器获取了多少数据?再回想一下我们实现上传的过程,我们是通过 request.binaryread(request.totalbytes)来实现的,在request的过程中我们无法得知当前服务器获取了多少数据。所以只能通过变通的方法了,如果我们可以将获取的数据分成一块一块的,然后根据已经上传的块数我们就可以算出来当前上传了多大了!也就是说,如果我 1k为1块,那么上传1mb的输入流就分成1024块来获取,例如我当前已经获取了100块,那么就表明当前上传了100k。当我提出分块的时候很多人觉得不可思议,因为他们都忽略binaryread方法不仅是可以读取指定大小,而且可以连续读取的。

    写个例子来验证一下分块读取的完整性,在刚才的例子基础上(注意该示例不要上传大文件,否则可能会造成浏览器死掉):

    <%
    dim bidata, postdata, totalbytes, chunkbytes
    chunkbytes = 1 * 1024 分块大小为1k
    totalbytes = request.totalbytes 总大小
    postdata = "" 转化为文本类型后的数据
    readedbytes = 0 初始化为0
    分块读取
    do while readedbytes < totalbytes
    bidata = request.binaryread(chunkbytes) 当前块
    postdata = postdata & binarytostring(bidata,chunkbytes) 将当前块转化为文本并拼接
    readedbytes = readedbytes + chunkbytes 记录已读大小
    if readedbytes > totalbytes then readedbytes = totalbytes
    loop
    response.write "<pre>" & postdata & "</pre>" 使用pre,原样输出格式
    将二进制流转化成文本
    function binarytostring(bidata,size)
    const adlongvarchar = 201
    set rs = createobject("adodb.recordset")
    rs.fields.append "mbinary", adlongvarchar, size
    rs.open
    rs.addnew
    rs("mbinary").appendchunk(bidata)
    rs.update
    binarytostring = rs("mbinary").value
    rs.close
    end function
    %>

    试验一下上传刚才的文本文件,输出结果证明这样分块读取的内容是完整的,并且在while循环中,我们可以在每次循环时将当前状态记录到application中,然后我们就可以通过访问该application动态获取上传进度条。

    另:上例中是通过字符串拼接的,如果是要拼接二进制数据,可以通过adodb.stream对象的write方法,示例代码如下:

    set bsourcedata = createobject("adodb.stream")
    bsourcedata.open
    bsourcedata.type = 1 binary
    do while readedbytes < totalbytes
    bidata = request.binaryread(chunkbytes)
    bsourcedata.write bidata 直接使用write方法将当前文件流写入bsourcedata中
    readedbytes = readedbytes + chunkbytes
    if readedbytes > totalbytes then readedbytes = totalbytes
    application("readedbytes") = readedbytes
    loop


    三、保存上传的文件
    通过request.binaryread获取提交数据,分离出上传文件后,根据数据类型的不同,保存方式也不同:

    对于二进制数据,可以直接通过adodb.stream对象的savetofile方法,将二进制流保存成为文件。
    对于文本数据,可以通过textstream对象的write方法,将文本数据保存到文件中。
    对于文本数据和二进制数据,是可以方便的相互转换的,对于上传小文件来说,两者基本上没什么差别。但是两种方式保存时还是有一些差别的,对于 adodb.stream对象,必须将所有数据全部装载完才可以保存成文件,所以使用这种方式如果上传大文件将很占用内存,而对于textstream对象,可以在文件创建好后,一次write一部分,分多次write,这样的好处是不会占用服务器内存空间,结合上面分析的分块获取数据原理,我们可以每获取一块上传数据就将之write到文件中。我曾做过试验,同样本机上传一个200多mb的文件,使用第一种方式内存一直在涨,到最后直接提示计算机虚拟内存不足,最可恨是即使进度条表示文件已经上传完,但是最终文件还是没有保存上。而使用后一种方法,上传过程中内存基本上无什么变化。

    四、未解决的难题
    我在博客园上看到bestcomy描述他的asp.net上传组件是可以和sever.settimeout无关的,而在asp中我是没能做到,对于上传大文件,就只有将server.settimeout设置为一个很大的值才可以。不知道有没有比较好的解决方法。

    如果我们在保存文件时,使用textstream对象的write方法,那么如果用户上传时中断了文件传输,已经上传的那部分文件还是在的,如果可以断点续传就好了。关键问题是request.binaryread方法虽然可以分块读取,但是却不能跳过某一段读取!

    五、结束语
    原理基本上是说清楚了,但是实际代码要比这复杂的多,要考虑很多问题,最麻烦在分析数据那部分,对于每一块获取的数据,要分析是不是属于描述信息,是表单项目还是上传的文件,文件是否已经上传结束……

    相信根据上面的描述,您也可以开发出您自己功能强大的无组件上传组件。我想更多的人关心的只是代码,而不会自己动手去写的,也许没有时间,也许水平还不够,更多的只是已经成为了一种习惯……我在csdn上见过太多技术八股文——一段说明,然后全是代码。授人以鱼不若授人以渔,给你一个代码,也许你并不会去思考为什么,直接拿去用,当下次碰到类似的问题的时候,还是不知道为什么,希望此文能让更多人学到点什
    文章整理:站长天空 网址:http://www.z6688.com/
    以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!
  • 相关阅读:
    BIEE变量总结
    微信支付回调问题
    内网搭建WEB服务器教程(转载)
    c#简体繁体转换
    js页面之间函数调用
    数据库性能优化一:SQL索引一步到位
    EasyUI兼容IE问题
    SQL函数说明大全
    经典SQL语句大全(绝对的经典)
    Sql Server 常用系统存储过程大全
  • 原文地址:https://www.cnblogs.com/pricks/p/1660040.html
Copyright © 2020-2023  润新知