在这个数字化时代,实时协作变得越来越重要。无论是在商业环境中,还是在教育领域,人们都希望能够实时地共享和编辑文档。本文将介绍如何开发一个允许用户实时协作编辑文档的系统。
本文介绍的代码是作者自己编写的一系列库的集合,包括:
该库采用客户端-服务器模式。服务器监听一个可访问的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()方法更新。
还可以调用:
使用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 & 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();
}
参数e和sze是文档的当前字节数组和大小,d和sz是diff。该函数在vector
每个客户端由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,最小化网络使用。
压缩包包含:
2017年6月20日:更新了RWMutex、tlock和库。