• 文件断点续传原理与实现—— ESFramework 通信框架4.0 进阶(12)


    ESFramework通信框架 4.0 快速上手(13) -- 文件传送,如此简单一文的详细介绍和ESFramework通信框架 4.0 快速上手(14) -- 聊天系统Demo,增加文件传送功能(附源码)一文的Demo中,我们已经尝试了ESFramework通信框架提供的文件传送功能和断点续传特性。支持断点续传是非常有意义的,比如当我们使用了1个小时的时间上传或下载一个大文件到99%的时候,网络突然断一下,就要全部重头再来,这实在是不能忍受的。现在,我们就解释一下ESFramework通信框架中文件断点续传的实现原理。

    1.文件断点续传的功能由接收方管理。

    2.在每次文件接收中断的时候,会创建一个等待续传的项目对象,使用ResumedFileItem类来封装相关的信息。

    复制代码
        /// <summary>
        /// 有可能被续传的项目。
        /// </summary>
        public class ResumedFileItem
        {
            #region SenderID
            private string senderID;
            /// <summary>
            /// 发送者的ID。
            /// </summary>
            public string SenderID
            {
                get { return senderID; }
                set { senderID = value; }
            } 
            #endregion

            #region OriginFilePath
            private string originFilePath;
            /// <summary>
            /// 发送方发送的文件的全路径。
            /// </summary>
            public string OriginFilePath
            {
                get { return originFilePath; }
                set { originFilePath = value; }
            } 
            #endregion

            #region OriginFileSize
            private long originFileSize = 0;
            /// <summary>
            /// 发送方发送的文件的大小。
            /// </summary>
            public long OriginFileSize
            {
                get { return originFileSize; }
                set { originFileSize = value; }
            } 
            #endregion

            #region OriginFileLastUpdateTime
            private DateTime originFileLastUpdateTime;
            /// <summary>
            /// 发送方发送的文件的最后修改日期。
            /// </summary>
            public DateTime OriginFileLastUpdateTime
            {
                get { return originFileLastUpdateTime; }
                set { originFileLastUpdateTime = value; }
            } 
            #endregion

            #region LocalTempFileSavePath
            private string localTempFileSavePath;
            /// <summary>
            /// 接收的临时文件的存储路径
            /// </summary>
            public string LocalTempFileSavePath
            {
                get { return localTempFileSavePath; }
                set { localTempFileSavePath = value; }
            } 
            #endregion

            #region LocalFileSavePath
            private string localFileSavePath;
            /// <summary>
            /// 接收的文件存储路径
            /// </summary>
            public string LocalFileSavePath
            {
                get { return localFileSavePath; }
                set { localFileSavePath = value; }
            }
            #endregion

            #region ReceivedCount
            private long receivedCount = 0;
            /// <summary>
            /// 已经接收了多少字节数。
            /// </summary>
            public long ReceivedCount
            {
                get { return receivedCount; }
                set { receivedCount = value; }
            } 
            #endregion

            #region DisrupttedTime
            private DateTime disrupttedTime = DateTime.Now;
            /// <summary>
            /// 接收中断的时间。
            /// </summary>
            public DateTime DisrupttedTime
            {
                get { return disrupttedTime; }
                set { disrupttedTime = value; }
            } 
            #endregion
        }
    复制代码

        为何要记录如此多的信息了?这是为了当发送方再次发送同一文件给接收方时,接收方可以依据这些信息进行匹配,是否可以找到一个续传的项目,如果匹配成功,则表示可以进行续传。

          要记录的这些信息从哪里获取了?首先,是发送方在发送文件的时候,会将文件的相关信息告知接收方,如 被发送文件的全路径、被发送文件的大小、被发送的文件的最后修改日期等等;其次,当接收方接收文件时,就知道了临时文件的存储路径、正式文件的存储路径;再次,当文件传输中断的时刻,可以知道当前的文件已经被传递了多少字节。有了这些信息,断点续传才成为可能。  

    3.使用IResumedFileManager将所有的续传项目管理起来。

    复制代码
        public interface IResumedFileManager
        {
            /// <summary>
            /// 当一个续传项超过多长时间没被使用,则将其移除,同时删除对应的临时文件。单位:秒。默认值:600。
            /// </summary>
            int TTL4ResumedFileItem { get; set; }

            /// <summary>
            /// 是否启用文件的断点续传功能。默认值 true。
            /// </summary>
            bool Enabled { get; set; }

            IAgileLogger EsfLogger { set; }

            void Initialize();

            void Add(ResumedFileItem resumedFileItem);

            /// <summary>
            /// 当接收一个文件之前,先看能否找到续传的匹配项(同时判断临时文件是否存在)。如果没有匹配,则返回null。
            /// </summary>
            ResumedFileItem Mapping4Receive(TransmittingFileInfo fileInfo);

            // <summary>
            /// 移除续传项。
            /// </summary>
            /// <param name="resumedFileItem">要移除的续传项</param>
            /// <param name="deleteTempFile">是否同时删除临时文件</param>
            void Remove(ResumedFileItem resumedFileItem, bool deleteTempFile);        
        }
    复制代码

        ResumedFileManager提供了定时删除过期的续传项目的功能,比如,一个续传项目在文件中断传输以后的10分钟内都没被激活,则视其为过期项目,将会被ResumedFileManager从内存中删除。这样就避免累积很多无效的续传项目对象,而浪费内存。

          ResumedFileManager的Remove方法用于从内存中移除某个续传项目,其第二个参数表示是否删除临时文件,当某个续传项目因过期而被删除时,这时必须将临时文件也删除掉,以释放硬盘空间 -- 特别是对于作为文件服务器的服务端,这点更是重要的,否则,众多永远也不会被用到的临时文件将会浪费大量的硬盘空间。如果是因为找到匹配项而从管理器中移除续传项时,则不能删除临时文件,因为接下来临时文件会被继续使用。

    4.当接收方在接收一个文件之前,首先调用IResumedFileManager的Mapping4Receive方法寻找续传匹配项。当同时满足三个条件时(即OriginFilePath、OriginFileSize、OriginFileLastUpdateTime),匹配才算成功。

    if (item.OriginFilePath == fileInfo.OriginFilePath && item.OriginFileSize == fileInfo.FileSize && item.OriginFileLastUpdateTime == fileInfo.OriginFileLastUpdateTime)
                        {
                            return item;
                        }

          如此,可以保证续传的文件与上次传输中断的文件是同一个文件。

    5.当找到匹配项时,可以询问用户是否接受续传,ESPlus通过回调IFileBusinessHandler.ReadyToAcceptFileAsyn方法得到回复。

     string ReadyToAcceptFileAsyn(string senderID, string fileName, long fileLength, string comment, string fileID, ResumedFileItem resumedFileItem);             

          如果返回值为null,表示拒绝接收;如果返回的存储路径与上次的存储路径相同,则表示同意续传;如果返回新的路径,则表示要求发送方重新发送整个文件。框架根据该方法的返回值而决定是否在后台启动续传。

    6.如果同意续传,则接收方会告知发送方已接收的字节数,发送方可以从文件的对应偏移处开始发送后续数据。RejectOrAcceptFileContract协议说明了这一点。

    复制代码
        public class RejectOrAcceptFileContract
        {
            #region FileID
            private string fileID;
            public string FileID
            {
                get { return fileID; }
                set { fileID = value; }
            } 
            #endregion

            #region Agree
            private bool agree;
            public bool Agree
            {
                get { return agree; }
                set { agree = value; }
            } 
            #endregion      

            #region ReceivedCount4ResumeFile
            private long receivedCount4ResumeFile = 0;
            /// <summary>
            /// 如果值非0,则表示续传,指示已经传递了多少字节。
            /// </summary>
            public long ReceivedCount4ResumeFile
            {
                get { return receivedCount4ResumeFile; }
                set { receivedCount4ResumeFile = value; }
            } 
            #endregion
        }
    复制代码

      

    7.如果续传开始,则接收方首先需要打开原始的临时文件,并寻址到正确的偏移处。

     this.fStream = new FileStream(this.tempFilePath, FileMode.Open);
     this.fStream.Seek(item.ReceivedCount, SeekOrigin.Begin);

      

    8.接下来就和以前一样进行正常的文件传输了。这样的设计,可以支持同一文件的无限次断点续传,也就是说,你在传送一个巨大文件的时候,哪怕中途断网很多次,也没关系,你不需要额外地重复传递哪怕一个字节。

  • 相关阅读:
    二叉排序树
    二叉树遍历
    二叉搜索树
    哈夫曼树
    括号匹配问题,栈
    A、B两伙马贼意外地在一片沙漠中发现了一处金矿,双方都想独占金矿,但各自的实力都不足以吞下对方,经过谈判后,双方同意用一个公平的方式来处理这片金矿。处理的规则如下:他们把整个金矿分成n段,由A、B开始轮流从最左端或最右端占据一段,直到分完为止。 马贼A想提前知道他们能分到多少金子,因此请你帮忙计算他们最后各自拥有多少金子?(两伙马贼均会采取对己方有利的策略)
    小明同学喜欢体育锻炼,他常常去操场上跑步。跑道是一个圆形,在本题中,我们认为跑道是一个半径为R的圆形,设圆心的坐标原点(0,0)。小明跑步的起点坐标为(R,0),他沿着圆形跑道跑步,而且一直沿着一个方向跑步。回到家后,他查看了自己的计步器,计步器显示他跑步的总路程为L。小明想知道自己结束跑步时的坐标,但是他忘记自己是沿着顺时针方向还是逆时针方向跑的了。他想知道在这两种情况下的答案分别是多少。
    大家希望能选出一个人,所有人都认识他,但同时他不认识镇上除自己以外的其他人(在此,我们默认每个人自己认识自己)。可是小镇里的人太多了,一下子大家谁也说服不了谁
    你要的最后一个字符就在下面这个字符串里,这个字符是下面整个字符串中第一个只出现一次的字符。(比如,串是abaccdeff,那么正确字符就是b了)
    面经一
  • 原文地址:https://www.cnblogs.com/sylone/p/6097013.html
Copyright © 2020-2023  润新知