好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

基于SignalR的超线程上载器

基于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,当然也没有优化,还请大家自行测试吧.

代码下载

请点击这里下载

 

 

分类:  Javascript&JQuery

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于基于SignalR的超线程上载器的详细内容...

  阅读:37次