如何实现离线文件?

      近段时间,有几个朋友问我如何实现类似QQ离线文件的功能。不想一一作答,就写一篇博文来比较完整的解释这个问题。

      所谓“离线文件”,就是当接收者不在线时,发送者先把文件传送给服务端,在服务器上暂时保存,等接收者上线时,服务端再把文件发送给他。当然,要想实现离线文件的功能,其最基本的前提是要先实现传送文件的功能,我们就以ESFramework提供的传送文件的功能为基础,在其之上一步步完成一个基本的离线文件功能。

      下面我们就用户在使用离线文件时,按各个动作发生的先后顺序,介绍程序方面与之对应的设计与实现。

1.客户端发送离线文件

      当用户选择好一个文件,并点击“发送离线文件”按钮时,其目的是要将这个文件传送给服务端,这可以直接使用IFileOutter的BeginSendFile方法:

    /// <summary>
    /// 发送方准备发送文件(夹)。/// </summary>
    /// <param name="accepterID">接收文件(夹)的用户ID</param>
    /// <param name="fileOrDirPath">被发送文件(夹)的路径</param>
    /// <param name="comment">其它附加备注。如果是在类似FTP的服务中,该参数可以是保存文件(夹)的路径</param>
    /// <param name="projectID">返回文件传送项目的编号</param>
    void BeginSendFile(string accepterID, string fileOrDirPath, string comment, out string projectID);

      如果将参数accepterID传入null,表示文件的接收者就是服务端。那么我们要如何区分,这不是一个最终由服务端接收的文件,而是要传给另一个用户的离线文件了?这里,我们可以巧用comment参数,比如,comment参数如果为null,就表示普通的上传文件;comment不为null,就表示一个离线文件,并且其值就是文件最终接收者的ID。(当然,如果在你的项目中,comment参数已经有了其它用途,我们可以进一步扩展它,加上一些标签,使其能够标志出离线文件)。

      下面这个调用示例,就是将Test.txt文件离线发送给aa01。

    string filePath = ...; //要发送文件的路径
    string projectID = null;
    fileOutter.BeginSendFile(null, filePath, "aa01", out projectID);

2.服务端接收离线文件     

      客户端调用BeginSendFile方法请求发送文件后,服务端会触发IFileController的FileRequestReceived事件。同理,我们判断该事件的comment参数,当其不为null时,表示是个离线文件。在答复客户端同意接收文件之前,我们需要先将离线文件的相关信息保存起来,这里我们使用OfflineFileItem类来封装这些信息。

    /// <summary>
    /// 离线文件条目
    /// </summary>
    public class OfflineFileItem
    {
        /// <summary>
        /// 条目的唯一编号,数据库自增序列,主键。
        /// </summary>
        public string AutoID { get; set; }

        /// <summary>
        /// 离线文件的名称。
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// 文件的大小。
        /// </summary>
        public ulong FileLength { get; set; }

        /// <summary>
        /// 发送者ID。
        /// </summary>
        public string SenderID { get; set; }

        /// <summary>
        /// 接收者ID。
        /// </summary>
        public string AccepterID { get; set; }

        /// <summary>
        /// 在服务器上存储离线文件的临时路径。
        /// </summary>
        public string RelayFilePath { get; set; }
    }

        有了OfflineFileItem的定义之后,我们就可以处理IFileController的FileRequestReceived事件了。 

   rapidServerEngine.FileController.FileRequestReceived += new CbFileRequestReceived(fileController_FileRequestReceived);

   ObjectManager<string, OfflineFileItem> offlineFileItemManager = new ObjectManager<string, OfflineFileItem>(); //可以把ObjectManager类看作一个线程安全的Dictionary。
    void fileController_FileRequestReceived(string projectID, string senderID, string fileName, ulong totalSize, ResumedProjectItem resumedFileItem, string comment)
    {
       string saveFilePath = "......" ;//根据某种策略得到存放文件的路径
       if (comment != null) //根据约定,comment不为null,表示为离线文件,其值为最终接收者的ID。
        {
            string accepterID = comment;
            OfflineFileItem item = new OfflineFileItem();
            item.AccepterID = accepterID;
            item.FileLength = totalSize;
            item.FileName = fileName;
            item.SenderID = senderID ;
            item.RelayFilePath = saveFilePath;
            offlineFileItemManager.Add(projectID, item);
        }

        //给客户端回复同意,并开始准备接收文件。
        rapidServerEngine.FileController.BeginReceiveFile(projectID ,saveFilePath);
    }      

      上面的代码做了三件事情:

(1)根据某种策略得到存放文件的路径。

(2)创建一个离线文件信息条目,保存在内存中。

(3)回复客户端,并准备接收文件。

    需要重点说明的是第一点,对于一般的小型项目,在服务端我们可以将所有的离线文件存放在当前服务器的某个目录下;但是对于大型项目,一般需要使用DFS(分布式文件系统)来存储这些临时的离线文件。

    客户端收到服务器的回复后,会正式开始传送文件,如果传送过程中,因为某种原因导致传送中断,则服务端会触发IFileController.FileReceivingEvents的FileTransDisruptted事件。在该事件处理函数中,我们从内存中移除对应的离线文件信息条目: 

    rapidServerEngine.FileController.FileReceivingEvents.FileTransDisruptted += new CbGeneric<TransferingProject, FileTransDisrupttedType>(fileReceivingEvents_FileTransDisruptted);

    void fileReceivingEvents_FileTransDisruptted(TransferingProject project, FileTransDisrupttedType type)
    {
        offlineFileItemManager.Remove(project.ProjectID);
    }

      如果文件正常传送完毕,则服务端会触发IFileController.FileReceivingEvents的FileTransCompleted事件。此时,我们将对应的离线文件信息条目从内存转移存储到数据库中,以防止服务器重启时导致信息丢失: 

    rapidServerEngine.FileController.FileReceivingEvents.FileTransCompleted += new CbGeneric<TransferingProject>(fileReceivingEvents_FileTransCompleted);

    IOfflineFilePersister offlineFilePersister = ......;
    void fileReceivingEvents_FileTransCompleted(TransferingProject project)
    {
        OfflineFileItem item = offlineFileItemManager.Get(project.ProjectID);
        offlineFilePersister.Add(item);
        offlineFileItemManager.Remove(project.ProjectID);
    }

       我们设计IOfflineFilePersister接口,用于与数据库中的OfflineFileItem表交互。 

    public interface IOfflineFilePersister
    {
        /// <summary>
        /// 将一个离线文件条目保存到数据库中。
         /// </summary>
        void Add(OfflineFileItem item);

        /// <summary>
        ///  从数据库中删除主键值为ID的条目。
         /// </summary>
        void Remove(string id);

        /// <summary>
        /// 从数据库中提取接收者为指定用户的所有离线文件条目。
         /// </summary>
        List<OfflineFileItem> GetByAccepter(string accepterID);
    }

       我们可以使用ADO.NET或者EntityFramework实现上述接口。

3.服务端发送离线文件给最终接收者

      当真正的接收者上线时,服务端要把相关的离线文件发送给他。通过预定UserManager的SomeOneConnected事件,我们知道用户上线的时刻。 

    rapidServerEngine.UserManager.SomeOneConnected += new CbGeneric<UserData>(userManager_SomeOneConnected);

    void userManager_SomeOneConnected(UserData data)
    {
        List<OfflineFileItem> list = offlineFilePersister.GetByAccepter(data.UserID);
        if (list != null)
        {
            foreach (OfflineFileItem item in list)
            {
                string projectID = null ;
                rapidServerEngine.FileController.BeginSendFile(item.AccepterID, item.RelayFilePath, item.SenderID, out projectID);
                offlineFilePersister.Remove(item.AutoID);
                File.Delete(item.RelayFilePath);
            }
        }
    }

      上面的代码做了三件事情:

(1)从数据库中查找所有接收者为登录用户的离线文件信息条目。

(2)将离线文件逐个发送给这个用户

(3)从数据库中删除相应的条目,从磁盘上删除对应的离线文件。

      实际上,第(3)点我们可以延迟到文件发送完成时,才执行删除操作。这样,就可以在发送万一意外中断时,使得重新发送成为可能。

      客户端接收到服务端的发送文件请求时,会触发IFileOutter的FileRequestReceived事件,此时也可以根据comment参数的内容,来判断其是否为离线文件。后续的步骤的实现就相当容易了,这里就不再赘述了。

 

      本文简洁地描述了实现离线文件功能的主要思路和基本模型,在实际的项目开发时,可以根据具体的需求在这个模型的基础上,进一步完善,包括很多细节和异常处理都需要加入进来。

时间: 2024-09-12 10:24:26

如何实现离线文件?的相关文章

Win7系统任务栏没显示QQ图标却可以接收离线文件怎么办

  Win7系统任务栏没显示QQ图标却可以接收离线文件怎么办           1.按"ctrl+alt+z"快捷键调出QQ面板; 2.在QQ面板中点击左下角的"系统设置"按钮; 3.在打开的"系统设置"窗口中,切换到"主面板"标签页中,在"在任务栏通知区域显示QQ图标"选项前面打勾.

阿里旺旺怎样发送离线文件给陌生人?

  阿里旺旺发送离线文件给陌生人之特殊情况: 1.有过交易的双方,算作最近联系人 ; 2.互相没有加好友,但是互相旺旺说过话(要双方都回应过),重新登陆旺旺后算作最近联系人; 3.没有旺旺联系过,A加B为好友,B没有加A,则B可以发送离线文件给A,A不能发送离线文件给B

阿里旺旺离线文件在哪?

  最近,有一位淘宝用户通过阿里旺旺向小编发送了离线文件.不过,小编却不知道阿里旺旺离线文件在哪下载.这可难为死小编了.经过小编反复研究之后才发现,要下载阿里旺旺离线文件需要先下载跟安装阿里旺旺客户端.下面,一起来看看具体的方法! 阿里旺旺 在电脑中安装阿里旺旺,登录阿里旺旺,就可以查收到离线文件的接受请求了.需要说明的是:阿里旺旺离线文件会在服务器上保存七天.在这期间,只要登录就会提示有离线文件要传给你哦!接收就行了.超期的话,离线文件就被自动删除了!

电脑端QQ提示服务器拒绝了您发送离线文件的解决方法

  1.当文件不论大小,都无法发送的话,可能是因为非会员会有限制,如每天只能发送10MB离线文件,或者每天离线文件发送次数不可超过100次等情况,如果是这样,可以将文件进行压缩,一次性发送即可; 2.如果是只发送一条离线文件,并且文件不大的情况下依然会出现上述提示,有可能就是将QQ的安全级别设置得太高了,可以从QQ面板下方打开系统设置,然后进入安全级别设置,将安全级调低即可.

QQ服务器拒绝发送离线文件解决教程

给各位QQ软件的用户们来详细的解析分享一下QQ服务器拒绝发送离线文件的解决教程. 教程分享: 服务器拒绝了您发送离线文件的原因: 第一种:传输文件容量超过限制;实际上所有的普通QQ用户都可用使用QQ离线文件功能,只不过普通用户只允许每天传输"10M"的文件数据,一旦当天离线传输的文件容量大于了10M,腾讯就会提示"服务器拒绝了您发送离线文件",详细权限分配,请参照QQ用户离线文件存放空间大小限制表; 普通用户:10M/天 VIP1:200M/天 VIP2:300M/

QQ离线文件发送后怎么撤回?离线文件撤回教程

1.如果文件正在发送过程我们可以点击"取消"就可以撤消文件发送了,但是很小的文件几乎0秒就发了,那要怎么撤回呢 2.如果像上面说的小文件己经发送了那么我们要怎么撤回已经发送的离线文件呢? 3.如下图所示如果对方没有接受文件我们要离线文件旁边有一个小图标,我们点击它就会看到有一个"撤回"菜单了,我们点击它. 4.点击撤回时会有提示,因为我们要撤回所以只要点击"是"即可. 5.如果我们撤回成功了,会有如下提示 6.但是有一点提示的是我们撤回成功了,但

qq离线文件可以再次接收?qq离线文件再次接收技巧

法一.如果我们在本电脑中可以找到聊天记录,然后在聊天记录中会看到离线文件消息了,此时我们点击即可接收了. 法二.把QQ关了再重启QQ就又会跳出来(这种可能不出现). 法三.在QQ面板中找到'文件管理"然后切换到"离线文件"点击即可接受了.

tplink设置URL过滤后,QQ离线文件、群共享等无法使用?

在某些情况下,希望路由器下面的电脑只能登录QQ不能访问网页,在路由器 行为管控 > 应用限制 页面设置只允许QQ,其他禁止,在 行为管控 > 网址过滤 > URL过滤 页面禁止访问关键字"."(不能访问网页),如下图:     按照以上的方法设置后可以实现只能登录QQ无法访问网页的需求,但是出现了QQ离线文件不能接收和发送.群共享文件不能上传和下载等问题.   问题分析   由于QQ离线文件的接受和发送都是使用HTTP协议的(直接传输文件使用的是UDP),而URL过滤

QQ,6.7正式版发布:全新登陆框,增离线文件收藏

QQ,6.7正式版发布:全新登陆框,增离线文件收藏12月12日消息,腾讯今天发布了QQ,6.7正式版,带来了全 新的登陆框和视觉体验.此外,新版还提集成式聊天窗口.导出手机相册功能优化.离线文件,轻松一键即可收藏等贴心的新功能.QQ,6.7新版特性:1.收藏再编辑,收藏内容随心更改:2.离线文件,轻松一键即可收藏:3.QQ收藏入驻Outlook,重要邮件随时备忘:4."我的手机页"可添加收藏,贴心无处不在:5.群禁言支持自定义禁言时长,操作更灵活.下载地址:http://im.qq.c