代码人生的小狗窝

一行行枯燥的代码,却描绘出人生的点点滴滴

您现在的位置是:首页>_.NET相关

【纯干货】四年前想解决的事情,今天才实验成功

发布时间:2019-08-20浏览(2246)

    【纯干货】4年前想解决的事情,今天才实验成功

    第一份软件开发工作的第一个星期(不算做试用期的一个星期,无薪水试用)。因为不是软件专业,也没有经过培训和相关工作经验。老板不放心,但还是让我试一试。做的第一件事情就是上传文件,实时看进度,并且上传后预览。预览的文件类型有word,ppt,excel,flash,视频按帧获取预览图。office文件是在服务器端转成html后显示出来。

    做的还满意,就留下来了,后来就在那个公司待了两年。

    没解决的事情

    上传大文件,分块上传,浏览器原生不支持,需要借助第三方插件。最根本的原因就是浏览器端的js考虑的安全问题,不允许读取文件内容。

    现在IE10和其他主流浏览器支持html5了,js内置Filereader对象,websocket,这些听听都酷毙了。

    重大意义的时刻:

    我实现了原生分块上传,道路非常崎岖,结果很美好。

    这一实现标识着,浏览器未来无可替代的地位。

    我只介绍干货部分,其他细节,相信有兴趣的人已经知道了。

    首先,分块上传,必须能在js内读取文件内容,filereader对象是关键。这是一个异步读取方法,必须在onload事件内获取文件内容。要真真的分块上传,靠onprogress读取文件进度是不够的,并且文件过大的时候浏览器会卡死。file的slice函数是关键,把文件内容分块,每一个onload事件触发,标志着一块内容读取完毕,且可以在该事件内把文件进一步处理,如上传。

    FileReader有四个read方法,

    asText,只建议用来读取文本文件

    asDataUrl,读取到的媒体文件可以直接用于src属性,或者html文件内容也可以读成DataURL

    asArrayBuffer,官方介绍说不能直接使用,需要借助DataView,如int8Array或int32Array。实际上这话是有条件的,既然设计出来肯定是有用处的,当websocket的binaryType='arraybuffer'时,该event.target.result是可以直接被send到服务器端的,服务器端接收类型是byte[],对应superwebsocket的NewDataReceived事件。目前的问题,当文件大小是2k的时候,服务器端可以接收;而当文件为18K的时候,superwebsocket报protocolerror错误。尚不知道临界值是什么,或者需要设置什么参数,rfc6455协议好像也没限制最大大小吧。还是去年看过这个协议,待会查查。

    webSocket的关键设置:

    client = new WebSocket("ws://127.0.0.1:2014");
    client.binaryType = "arraybuffer";//如果不设置该属性,就不能直接send字节数组到服务端。如果superwebsocket服务端报protocolerror则有可能是这个原因引起的

    webSocket.send方法在onload事件中调用:

    client.send(this.result);

    结合slice和onload事件分块上传的核心代码:

             var res = this.result;
                loaded += res.byteLength;
                if (loaded < fileSize)//继续读取下一块
                {
                    readBlob(loaded);
                    times += 1;
                    console.log("next block,times:" + times);
                }
                else {
                    //读取完成
                    console.log("done loaded:" + loaded + ",size:" + fileSize);
                }    

    slice的使用方法:

    function readBlob(start)
    {
        var blob = currentFile.slice(start, start + step);
       
        reader.readAsArrayBuffer(blob);
    }

     

         // 0             1                   2                   3
         // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
         //+-+-+-+-+-------+-+-------------+-------------------------------+
         //|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
         //|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
         //|N|V|V|V|       |S|             |   (if payload len==126/127)   |
         //| |1|2|3|       |K|             |                               |
         //+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
         //|     Extended payload length continued, if payload len == 127  |
         //+ - - - - - - - - - - - - - - - +-------------------------------+
         //|                               |Masking-key, if MASK set to 1  |
         //+-------------------------------+-------------------------------+
         //| Masking-key (continued)       |          Payload Data         |
         //+-------------------------------- - - - - - - - - - - - - - - - +
         //:                     Payload Data continued ...                :
         //+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
         //|                     Payload Data continued ...                |
         //+---------------------------------------------------------------+

    从帧结构来看,有8个字节表示内容长度,所以内容长度限制在哪设置的?有谁熟悉superwebsocket


    asBinaryString 现在不赞成使用的方法,且IE中也没有该方法。

    如果服务器端,不支持websocket又不想使用第三方支持如superwebsocket这样组件,可以直接使用ajax真真分块上传,确定是每次请求都会建立新的连接。

    使用ajax上传注意事项干货:

    建议把arraybuffer转成int32array,这样客户端转换较快。服务器端用int32[]接收;如果int8array服务端也可以用int32接收。

    重点:async:false 保证顺序;也可以每次上传的时候额外带参序号,服务器端重新组装顺序。

    Int8array的话,服务端可以直接把每个元素转成byte

      [System.Web.Mvc.HttpPost]
            public ActionResult File(int[] datas)
            {
                if (datas != null)
                {
                    var d = datas.ToList().ConvertAll(x => (byte)x).ToArray();
                }
                return Content("OK");
            }

     

    int32array的话,服务端可以借助bitconverter.getbytes(int)方法。

     [System.Web.Mvc.HttpPost]
            public ActionResult File(int[] datas)
            {
                if (datas != null)
                {
                    List<byte> bs = new List<byte>();//接收到的文件缓冲对象
                    for (int i = 0; i < datas.Length; i++)
                    {
                        foreach (var item in BitConverter.GetBytes(datas[i]))
                        {
                            bs.Add(item);
                        }
                    }
                }
                return Content("OK");
            }

    ajax客户端上传关键方法:

      $.ajax("/Home/File", {
                    data: { datas: new Int8Array(this.result) }, success: function (res) {
                        console.log(res);
                    },
                    async: false,
                    type:"post"
                });

    这个调用是位于FileReader的onload事件中。

    这个只是一个文件分块上传的例子,更多的好处是实时通信,如实时获取服务器端处理进度,而不用重复请求,把长连接,轮询,桥都抛在脑后吧。推送消息都成为可能。

    如果觉得有意义,别忘了点【推荐】

    6楼黑色王子
    支持!
    5楼on the way.
    找个Flash上传插件不就行了 PLupload
    Re: 冲杀
    @on the way.,引用找个Flash上传插件不就行了 PLupload,是能做,但是有时候会遇到浏览器兼容的问题,比如在某些浏览器下,不回发cookie,然后你就瓜了,因为你不可能不用cookie啊。。。。
    Re: Tony Tan
    @on the way.,of course.实现的方法很多。一来是兴趣,二来原生的支持好处是大大的,以后会更加普及
    4楼胖子黎
    支持!不过我觉得不是好适用吧。。
    Re: Tony Tan
    @胖子黎,引用支持!不过我觉得不是好适用吧。。,实用性时间自见分晓
    3楼紫砂壶
    office文件是在服务器端转成html后=====,这个是用什么实现的,我现在是用统一转为flv文件显示实现的。。
    Re: Tony Tan
    @紫砂壶,另存为html,类似这样的代码document.saveAs(quot;*.htmlquot;)
    Re: on the way.
    @紫砂壶,Office DCOM组建本身就能作
    2楼heguo
    收藏,我可能正要用到。
    1楼DownUp
    挺好奇的,楼主是0基础进公司的吗