如何实现离线文件?

近段时间,有几个朋友问我如何实现类似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-08-29 19:22:19

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

【转】可在广域网部署运行的QQ高仿版 -- GG叽叽V3.2,增加离线消息、离线文件功能(源码)

(几句题外话:虽然就如何将GG发展为一个有商业价值的产品,我还没有很清晰明确的思路,但是从GG发布以来,通过GG认识了一些朋友,也接了一些小单子,赚了一点小钱.有了一点甜头,目前和2.3个好朋友一起做做小项目也是不错的,这未尝不是一条养家糊口之路了?呵呵) 距离上次更新(GG叽叽V3.0,完善基础功能)正好有1个月了,在这个月中,我主要为GG增加了离线消息和离线文件的功能.之所以将这两个功能提前实现,是因为至GG发布以来,就有很多朋友问我在GG的基础上如何实现离线消息和离线文件.看来作为一个能用

HTML5项目笔记6:使用HTML5 FileSystem API设计离线文件存储

在移动环境或者离线环境中,WebDataBase 虽然能够存储并有效地管理和维护客户端的数据集合,但是仍不能满足对包含大段数据文件的存储和多种不同格式文件的保存,于是我们就需要离线的文件管理系统来维护我们工作了,基于HTML5的FileSystem API 就充当这这个角色. 通过这个FileSystem API,我们的Web应用程序可以阅读,浏览,编辑和操纵本地文件系统. FileSystem API的主要功能有: Reading and manipulating files: File/Bl

可在广域网部署运行的QQ高仿版 -- GG叽叽V3.2,增加离线消息、离线文件功能(源码)

(几句题外话:虽然就如何将GG发展为一个有商业价值的产品,我还没有很清晰明确的思路,但是从GG发布以来,通过GG认识了一些朋友,也接了一些小单子,赚了一点小钱.有了一点甜头,目前和2.3个好朋友一起做做小项目也是不错的,这未尝不是一条养家糊口之路了?呵呵) 距离上次更新(GG叽叽V3.0,完善基础功能)正好有1个月了,在这个月中,我主要为GG增加了离线消息和离线文件的功能.之所以将这两个功能提前实现,是因为至GG发布以来,就有很多朋友问我在GG的基础上如何实现离线消息和离线文件.看来作为一个能用

解决百度云离线文件因含有违规内容被系统屏蔽无法下载问题

最近网上风声查的很严呀,好多视频都无法下载,一下载就提示  离线文件因含有违规内容被系统屏蔽无法下载,上次下载老外的视频教程都说是违规内容,忍无可忍,最近发现了一个神器,BTEditor ,应该算是种子文件编辑器吧,貌似能把那些被屏蔽的资源弄成可以离线下载的,网址   http://www.bteditor.com, 大家可以有空试试. 最近网上风声查的很严呀,好多视频都无法下载,一下载就提示  离线文件因含有违规内容被系统屏蔽无法下载,上次下载老外的视频教程都说是违规内容,忍无可忍,最近发现了

[其他]Android SDK离线文件路径以及安装更新方法

一.离线安装Android SDK文件路径 转载自:http://www.oschina.net/code/snippet_1539302_45940 Google TV Addon, Android API13, revision 1 https://dl-ssl.google.com/android/repository/google_tv-13_r01.zip Android SDK Platform-tools,revision 21 [*] http://dl-ssl.google.c

windows系统清理磁盘临时文件,及缓冲文件,及离线文件和空闲文件

cleanmgr.exe是微软系统内置的一个小程序,利用它,用户可以以"比较安全的 状态清理系统垃圾".事实上,它也就是磁盘清理.们在Windows Vista的开始菜单中输入cleanmgr 即可打开它,我们选择一个磁盘分区,Windows磁盘清理程序会自动扫描磁盘存在的各种垃圾问题,并计算出可以清理后可以节省出多少磁盘空间!

XMPP——Smack[5]文件传输及离线消息的获取

三天时间,赶在最后一下午实现了文件的传输,本来需要实现离线文件的发送的,一直没想好怎么弄,找openfire的离线文件插件没找到,后来想出一种方法,起服务器时起了一个系统用户,一直在线,当用户发送离线文件,检测到对方不存在,先发给系统用户,存到服务器路径,并在数据库中保存信息,当对方上线时,系统用户查表,拿文件发送 想是这么想的,问题是时间太紧,没有实现,囧. 下一篇写离线消息和离线文件 文件的发送 开一个文件选择框,选中文件后再调用下面的方法 [java] view plaincopyprin

Visual Studio 2017各版本安装包离线下载、安装全解析

转自 寂靜·櫻花雨 Visual Studio 2017各版本安装包离线下载.安装全解析 感谢IT之家网友 寂靜·櫻花雨 的投稿 关于Visual Studio 2017各版本安装包离线下载.更新和安装的方法以及通过已下载版本减少下载量的办法 微软最近发布了正式版Visual Studio 2017并公开了其下载方式,不过由于VS2017采用了新的模块化安装方案,所以微软官方并未提供ISO镜像,但是官方提供了如何进行离线下载的方案给需要进行离线安装的用户,只不过都是英文.本文将对官方指南中的一部

通达OA 一次升级引发的即时通讯工具不同接收离线信息的血案

今天上午,有工作新的进展需要跟领导沟通,就在OA上用精灵发了一条信息,因为领导显示离线状态,这就是一条离线信息,离线信息也没问题登陆后也可以看到. 过了1个多小时之后,领导打电话过来问我刚才说的工作的事情,我说给他发信息了,他说没收到.真的是很冤啊,我马上查历史记录看看是不是我记错了,明明就是发过了,记录 还在那里.突然我意识到这可能是OA系统的问题,因为隐约记得前几天也有一次类似的情形,他也说没收到信息. 我马上同一个屋里的同事进行了离线消息的测试,果然是离线信息收不到,竟然有这样的事情,天大