65.9K
CodeProject 正在变化。 阅读更多。
Home

实时协作:一个支持 RDC 的快速 C++ Windows 库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (16投票s)

2016年12月1日

CPOL

4分钟阅读

viewsIcon

20739

downloadIcon

212

轻松创建协作项目

引言

这是一个古老的梦想。我一直想创建这样的应用程序,允许我的所有用户(客户、学生、老师)在同一个文件上工作,并以有效的方式交换实时信息。

也许这也是你的梦想。如果是这样,请继续阅读。

背景

代码是我自己的一些库的集合

  • DIFF 库,使用远程差异压缩以实现更有效的更新
  • RWMutextlock,我自己的同步对象,用于提供复杂的锁
  • 我的一些小助手,包括 TEVENTXSOCKET,以及
  • collab.hpp,这里描述的库

该库采用客户端-服务器模式。服务器监听一个可访问的 TCP 端口,客户端连接到服务器,服务器将所有共享文档保存在本地或内存中。如果客户端更新了文档,服务器会使用更新数据通知所有其他客户端。

客户端身份验证

该库提供了一种抽象的方式来验证服务器或客户端,通过 AUTH 类。这个类的成员函数 Do() 返回一个 HRESULT。对于 E_PENDING,该函数将被再次调用。在身份验证成功时,函数必须返回一个 S_ 代码,而在失败时,它必须返回一个 E_ 代码。

该库提供了一个 ANYAUTH 类,它只返回 S_OK 用于测试目的。您可以传递您自己的身份验证机制。

客户端授权

同样的 AUTH 类的 Do() 函数可以在传递一个文档的 CLSID 时返回 S_FALSE。在这种情况下,该文件只能以只读方式打开,但客户端对该文件的更新将失败。

该库提供了一个 ANYAUTH 类,它只返回 S_OK 用于测试目的。您可以传递您自己的授权机制。

文档

每个文档由一个 CLSID 唯一标识。每个服务器可以托管无限数量的文档,并且每个客户端可以操作任意数量的文档。

服务器还维护客户端打开的所有文档的当前签名,因此当另一个客户端更新共享文件时,服务器可以知道如何更新客户端。

服务器端

服务器端包含在 SERVER 类中,该类包含所有文档的列表和所有已连接客户端的列表。每个客户端可以打开任意数量的文档。

//
COLLAB::ANYAUTH auth;
COLLAB::SERVER s(&auth, 8765,true); // Port 8765, and true to use filesystem instead of in-memory docs
s.Start(); // Start the server
...
...
...
s.End();   // Ends the server when we want to close it
//

您调用 SERVER::Start(),如果成功,它将返回 S_OK。服务器在后台线程中运行。当客户端连接时,会创建一个新线程。客户端向服务器执行以下请求

  • 创建或打开文档
  • 关闭文档
  • 获取文档的当前签名
  • 更新文档

当文档被更新时,所有已打开该文档的其他客户端将通过 SERVER::UpdateClientsOfDocument() 方法进行更新。

您也可以调用

  • void ForceUpdateDocument(GUID c) 以强制将文档更新到所有客户端
  • shared_ptr<DOCUMENT> GetDocument(GUID c) 获取指向文档的指针(如果您想锁定它并在客户端之外编辑它)。

使用 End() 结束服务器。此方法会立即关闭所有客户端。

客户端端

您首先需要一个 ON 结构,它在收到更新时提供通知

    class ON
        {
        public:
            virtual void Update(CLSID cid,const char* d,size_t sz) = 0;
        };

第一个参数是要更新的文档的 CLSID。其余的是文档的新数据(一个 DIFF 对象)。

为了重建整个文档,我们可以在多线程 COM 环境中使用此助手

void RecoFromDiff(const char* d, size_t sz, const char* e, size_t sze, vector<char>& o)
    {
    DIFFLIB::DIFF diff;
    DIFFLIB::MemoryRdcFileReader r1(e, sze);
    DIFFLIB::MemoryRdcFileReader diffi(d, sz);

    DIFFLIB::MemoryDiffWriter dw;
    diff.Reconstruct(&r1, &diffi, 0, dw);
    o = dw.p();
    }

参数 esze 是我们文档的当前字节数组和大小,而 d sz diff。该函数在 vector<char> 数组中重建完整的对象。

每个客户端由 COLLAB::CLIENT 类表示,该类包含对所有文档的引用以及 ON 通知类的 vector<>

//
COLLAB::ANYAUTH auth;
COLLAB::CLIENT c1(&auth);
MYON on; // some class that implements Update() of COLLAB::ON 
c1.AddOn(&on); // check below for ON class
c1.Connect("localhost",8765);
c1.Open(DOCUMENT_GUID); // If guid does not exist, server creates such a document
//
...
...
...
c1.Close(DOCUMENT_GUID);
...
...
...
c1.RemoveOn(&on);
c1.Disconnect();

如果文档存在,客户端将立即从服务器更新(请注意,如果您有自己的客户端更新版本,则必须在此初始更新之后将其推送到服务器)。

当您想要将更新发送到服务器时,您调用 CLIENT::Put()

HRESULT Put(GUID g, const char* d,size_t sz);

该函数从服务器请求文档签名,然后只将一个 diff 上传到服务器,从而最大限度地减少网络使用。

文件

zip 包含

  • 一个完整的协作演示解决方案,包含预构建的可执行文件和源代码
  • diff.hcollab.hpp 是您需要在自己的项目中包含的全部内容
  • 两个预构建的二进制文件,server.exe notepad.exe ,您可以使用它们创建同一文件的多个编辑器

历史

  • 2017-6-20:更新了 RWMutextlock 和库
  • 2016-12-1:首次发布
© . All rights reserved.