zoukankan      html  css  js  c++  java
  • 基于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 Microsoft.AspNet.SignalR, 然后就等着安装把,安装完毕以后,项目就变成了这个样子了.

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

    这里,我们先要在Global.asax中进行下路径映射: RouteTable.Routes.MapHubs();

    这段代码需要放到application_start中。

    然后我们创建一个类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,当然也没有优化,还请大家自行测试吧.

    代码下载

    请点击这里下载

  • 相关阅读:
    SpringMVC总结
    Linux执行.sh文件,提示No such file or directory的问题的解决方法
    eclipse下搭建shell脚本编辑器--安装开发shell的eclipse插件shelled
    Windows下调试hadoop
    【转】Markdown 的一些问题
    【转】论“我”和“我们”
    【转】什么是启发
    【转】Scheme 编程环境的设置
    【转】我为什么离开 Cornell
    【转】Currying 的局限性
  • 原文地址:https://www.cnblogs.com/scy251147/p/2950632.html
Copyright © 2011-2022 走看看