zoukankan      html  css  js  c++  java
  • 基于SignalR的超线程上载器

    基于SignalR的超线程上载器

    记得以前做过一个东西,就是当数据库有数据更新的时候,能够自动更新到前台,那时候signalr还没出现的时候,需要自己实现long pooling, 比较痛苦,反正是最终做完,效果也不是多么理想. 没想到最近几天发现了SignalR这个开源的东西,并且,它居然还被.net 4.0收录了. 怀着对实时交互性能的兴趣,于是便诞生了本文.

    效果演示

    下面我们先来看看演示(四个文件,前三个大小差不多,都为10MB左右,最后一个为400MB)(本演示在Firefox以及Chrome下演示通过,在IE7及其以下版本未通过.):

     

    看到了吧,多线程下载加上实时的通知功能,让webui变得非常不一般了.这也得益于Signalr将long pooling方式封装的非常好用,所以才会如此简便.

    那么,该如何来做呢?

    实现方式

    首先,我们需要安装SignalR包,这个微软都已经提供好了,我们需要用到的是VS2010的Package manager  console窗体,可以在Tools > library package manager处打开. 在使用这个工具之前,我们要确保机器已经安装了powershell 2.0,这个大家都知道怎么安装的.

    安装完毕以后,创建一个新的Web项目,然后请打开Package manager  console,然后输入Install-Package SignalR, 然后就等着安装把,安装完毕以后,项目就变成了这个样子了.

    从图中我们可以看到微软自动为我们引用了SignalR的类库和一堆的Javascript文件.好了,一切都准备好了,下面开工.

    首先我们创建一个类LetsChat.cs,然后这个类需要继承自Hub类,在类里面,我们需要实现send方法,为什么方法名字叫做send呢?这是一个约定. 然后我为这个类加上名称   [HubName("myChatHub")],那么前台js就可以通过这个hubname来访问类方法. 以下就是类里面具体的实现方式,大家不妨展开看一看,反正就是首先解析出文件路径,然后利用APM模式异步的利用文件流方式进行文件上传操作.

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Microsoft.AspNet.SignalR.Hubs;
    using System.Threading;
    using System.IO;
    using System.Reflection;
    
    namespace SignalRChat
    {
        [HubName("myChatHub")]
        public class LetsChat : Hub
        {
            public void send(string message)
            {
                if (string.IsNullOrEmpty(message))
                {
                    Clients.All.addMessage("文件内容为空,请检查!!");
                    return;
                }
    
                int fileCount = 0;
                
    
                if (message.Contains("|"))
                    fileCount = message.Split('|').Length;
                else
                    fileCount = 1;
    
                string[] fileCollection = new string[fileCount];
                if (fileCount > 1)
                    fileCollection = message.Split('|');
                else
                    fileCollection[0] = message;
    
    
                string uploadPath = AppDomain.CurrentDomain.BaseDirectory;
                int fileFlag = 0;
    
                foreach (string filename in fileCollection)
                {
                    if (File.Exists(filename))
                    {
                        string newName = Path.Combine(uploadPath,"Upload",FileWithOutExtension(filename));
                        if (File.Exists(newName))
                            try
                            {
                                File.Delete(newName);
                            }
                            catch (Exception ex)
                            {
                                Clients.All.addMessage(ex.Message);
                            }
                        parameterCollection p = new parameterCollection();
                        p.filename = filename;
                        p.newName = newName;
                        p.eachLoopSize = 2048;
                        p.fileFlag = fileFlag;
    
                        //Thread t = new Thread(new ParameterizedThreadStart(CopyFilesAsync));
                        //t.IsBackground = true;
                        //t.Start((object)p);
                        BeginCopy(p);
                        
                        fileFlag++;
                        
                    }
                }
            }
    
            private void BeginCopy(object obj)
            {
                try
                {
                    parameterCollection pCollection = (parameterCollection)obj;
                    Clients.All.addMessage("Start to copy " + pCollection.filename+ " now...");
                    Action<object> actionStart = new Action<object>(CopyFilesAsync);
                    actionStart.BeginInvoke(obj, new AsyncCallback(iar =>
                    {
                        Action<object> actionEnd = (Action<object>)iar.AsyncState;
                        actionEnd.EndInvoke(iar);
                        Clients.All.addMessage("Copied " + pCollection.filename + " ok...");
                    }), actionStart);
                }
                catch (Exception ex)
                {
                    Clients.All.addMessage(ex.Message);
                }
            }
    
            private struct parameterCollection
            {
                public string filename;
                public string newName;
                public int eachLoopSize;
                public int fileFlag;
            }
    
            private void CopyFilesAsync(object obj)
            {
                parameterCollection objConvert = (parameterCollection)obj;
                CopyFile(objConvert.filename, objConvert.newName, objConvert.eachLoopSize,objConvert.fileFlag);
            }
    
            
    
            ///<summary>
            ///复制文件
            ///</summary>
            ///<param name="fromFile">要复制的文件</param>
            ///<param name="toFile">要保存的位置</param>
            ///<param name="lengthEachTime">每次复制的长度</param>
            private void CopyFile(string fromFile, string toFile, int lengthEachTime,int fileFlag)
            {
                FileStream fileToCopy = null;
                try{fileToCopy = new FileStream(fromFile, FileMode.Open, FileAccess.Read);}
                catch (Exception ex) { Clients.All.addMessage(ex.Message); return; }
    
                FileStream copyToFile = null;
                try { copyToFile = new FileStream(toFile, FileMode.Append, FileAccess.Write); }
                catch (Exception ex) { Clients.All.addMessage(ex.Message); return; }
    
                string fileFlagStr = fileFlag.ToString();
                int lengthToCopy;
                int pauseCount=0; //主要是进行计数,然后调用Thead.sleep来是界面滑行更加流畅
    
                if (lengthEachTime < fileToCopy.Length)//如果分段拷贝,即每次拷贝内容小于文件总长度
                {
                    byte[] buffer = new byte[lengthEachTime];
                    int copied = 0;
                    while (copied <= ((int)fileToCopy.Length - lengthEachTime))//拷贝主体部分
                    {
                        lengthToCopy = fileToCopy.Read(buffer, 0, lengthEachTime);
                        fileToCopy.Flush();
                        copyToFile.Write(buffer, 0, lengthEachTime);
                        copyToFile.Flush();
                        copyToFile.Position = fileToCopy.Position;
                        copied += lengthToCopy;
    
                        //send to front UI
                        string sendSizeCurrent = ((double)copied / (double)fileToCopy.Length).ToString();
                        Clients.All.addMessage(fileFlagStr + "|" + sendSizeCurrent);
                        pauseCount++;
                        if (pauseCount % 3 == 0)
                            Thread.Sleep(1); //加上这个很重要,主要是让流能够有足够的事件写入,我们可以控制这里来让PrograssBar滑行的更流畅
                    }
                    int left = (int)fileToCopy.Length - copied;//拷贝剩余部分
                    lengthToCopy = fileToCopy.Read(buffer, 0, left);
                    fileToCopy.Flush();
                    copyToFile.Write(buffer, 0, left);
                    copyToFile.Flush();
    
                    Clients.All.addMessage(fileFlagStr + "|" + 1);
                }
                else//如果整体拷贝,即每次拷贝内容大于文件总长度
                {
                    byte[] buffer = new byte[fileToCopy.Length];
                    fileToCopy.Read(buffer, 0, (int)fileToCopy.Length);
                    fileToCopy.Flush();
                    copyToFile.Write(buffer, 0, (int)fileToCopy.Length);
                    copyToFile.Flush();
    
                    Clients.All.addMessage(fileFlagStr + "|" + 1);
                }
                fileToCopy.Close();
                copyToFile.Close();
                Thread.Sleep(10);
            }
    
    
            private string FileWithOutExtension(string filePath)
            {
                if (filePath.Contains(@"\"))
                    return filePath.Substring(filePath.LastIndexOf(@"\") + 1);
    
                if(filePath.Contains(@"/"))
                    return filePath.Substring(filePath.LastIndexOf(@"/") + 1);
                return filePath;
            }
        }
    }

    需要注意的是,在这里,我们可以利用Clients.All.addMessage(Message);来向前台打印出消息而不用刷新页面. 所以说,有了这个,我们就可以刷新进度,实时通知了.

    那么前台该怎么弄呢?

    首先,在chat.aspx页面,我引入如下的外部文件:

    View Code
    <script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.signalR-1.0.0-rc1.js" type="text/javascript"></script>
    <script src="signalr/hubs" type="text/javascript"></script>
    <link href="Css/main.css" rel="stylesheet" type="text/css" />

    记住的是, <script src="signalr/hubs" type="text/javascript"></script>一定要引用,虽然说文件并不存在.并且这个文件要放在jquery文件和signalR文件后面.

    然后在chat.aspx页面,我也输入如下的代码:

    View Code
    $(function () {
            //创建链接的实例
                var IWannaChat = $.connection.myChatHub;
                var count = 0;
    
                //浏览文件
                $("#btnBrowse").bind("click", function () {
                    $("#fileBrowe").click();
                    $("#fileBrowe").bind("change", function () {
                        var path = $(this).val();
                        if (path != null && path != "") {
                        //当选择好文件以后,就将文件路径信息加入到UI中.
                            $('#listFiles').append('<tr><td id="fileNameSpecific">' + path + '</td><td id="myPrograss' + (count) + '" "></td><td id="myState' + count + '">Ready</td></tr>');
                            count++;
                            preventDefault();
                        }
                    });
                });
    
                //点击上传按钮,将文件名称用竖线分割,然后发送到后台
                $("#btnUpload").bind("click", function () {
                    var resultFeed = "";
                    $("#listFiles td ").each(function (index, element) {
                        if (index % 3 == 0)  //get feed names and concreate.
                            resultFeed = $(this).text() + "|" + resultFeed;
                    });
                    if (resultFeed != null && resultFeed != "")
                    //将文件发送到后台
                        IWannaChat.server.send(resultFeed.substring(0, resultFeed.length - 1));
                });
    
                //这个主要是接收后台处理的结果,然后打印到前台来
                IWannaChat.client.addMessage = function (message) {
                    if (message.contains("|")) {
                        var result = message.split('|');
                        var fileFlag = result[0];
                        var filePrograss = result[1];
    
                        $('#myPrograss' + fileFlag).html('<table><tr><th  style="' + filePrograss * 200 + 'px;background-color:green;"></th><th style="line-height:10px;background-color:white;border:none;">' + parseInt(filePrograss * 100) + '%</th></tr></table>');
                        if (filePrograss != 1)
                            $('#myState' + fileFlag).html('In Prograss');
                        else
                            $('#myState' + fileFlag).html('Done');
                    }
                    else {
                        $("#log").append("<li>"+message+"</li>");
                    }
                };
    
                //开启(长轮训的方式)
                $.connection.hub.start();
            });
    
            String.prototype.contains = function (strInput) {
                return this.indexOf(strInput) != -1;
            }

    看完这些,你是不是感觉和微软提供的某个接口非常相像呢? 对,这就是ICallbackEventHandler,请参见我的文章BlogEngine学习二:基于ICallbackEventHandler的轻量级Ajax方式

    好了,就写到这里,这个demo刚做完,还有很多bug,当然也没有优化,还请大家自行测试吧.

    代码下载

    请点击这里下载

     
     
  • 相关阅读:
    【codecombat】 试玩全攻略 第二章 边远地区的森林 一步错
    【codecombat】 试玩全攻略 第十八关 最后的kithman族
    【codecombat】 试玩全攻略 第二章 边远地区的森林 woodlang cubbies
    【codecombat】 试玩全攻略 第二章 边远地区的森林 羊肠小道
    【codecombat】 试玩全攻略 第十七关 混乱的梦境
    【codecombat】 试玩全攻略 第二章 边远地区的森林 林中的死亡回避
    【codecombat】 试玩全攻略 特别关:kithguard斗殴
    【codecombat】 试玩全攻略 第二章 边远地区的森林 森林保卫战
    【codecombat】 试玩全攻略 第二章 边远地区的森林
    实验3 类和对象||
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2952024.html
Copyright © 2011-2022 走看看