Introduction
This is an old dream. I always wanted to create apps that allow all of my users (clients, students, teachers) to work on a same file and exchange realtime information in an efficient way.
Perhaps it is your dream too. If so, keep reading.
Background
The code is a collection of some of my own libraries:
- DIFF library, using Remote Differential Compression for more efficient updates
- RWMutex and tlock, my own synchronization objects to provide complex locks
- Some of my own small helpers, including
TEVENT
and XSOCKET
, and - collab.hpp, the library described here
The library is a client-server mode. The server listens to an accessible TCP port and the clients connect to the server which keeps all the shared documents locally or in memory. If a client updates the document, the server notifies all other clients with the update data.
Client Authentication
The library provides an abstract way to authenticate either the server or the clients, via the AUTH
class. This class' member function Do()
returns a HRESULT
. On E_PENDING
, the function is called again. On successful authentication, the function must return an S_
code, and on failure it must return an E_
code.
The library provides an ANYAUTH
class which simply returns S_OK
for testing purposes. You may pass your own authentication mechanism.
Client Authorization
The same AUTH
class Do()
function can return S_FALSE
when it is also passed a document's CLSID
. In this case, the file can only be opened for reading, but client updates to this file will fail.
The library provides an ANYAUTH
class which simply returns S_OK
for testing purposes. You may pass your own authorization mechanism.
The Document
Each document is identified uniquely by a CLSID
. Each server can host unlimited documents, and each client can manipulate any number of documents.
The server also maintains the current signature of all the documents opened by a client, so the server can know how to update a client when another client has updated a shared file.
The Server Part
The server part is contained in the class SERVER
, which contains a list of all documents and a list of all connected clients. Each client can open any number of documents.
COLLAB::ANYAUTH auth;
COLLAB::SERVER s(&auth, 8765,true); s.Start(); ...
...
...
s.End();
You call SERVER::Start()
which returns S_OK
on success. The server runs in a background thread. When a client connects, a new thread is created. The client performs requests to the server as follows:
- Create or open a document
- Close a document
- Get a documents current signature
- Update a document
When a document is updated, all other clients that have opened that document are updated via the SERVER::UpdateClientsOfDocument()
method.
You can also call:
void ForceUpdateDocument(GUID c)
to force updating a document to all clients shared_ptr<DOCUMENT> GetDocument(GUID c)
to get a pointer to the document (if you want to lock it and edit it outside the clients).
End the server using End()
. This method shuts down all clients immediately.
The Client Part
You need first an ON
structure, which provides notification when an update is received:
class ON
{
public:
virtual void Update(CLSID cid,const char* d,size_t sz) = 0;
};
The first parameter is the CLSID
of the document being updated. The rest are the new data for the document (a DIFF
object).
To reconstruct the entire document, we can use this helper in a multithreaded COM environment:
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();
}
Parameters e
and sze
are the current byte array and size of our document, and d
and sz
are the diff
. The function reconstructs the complete object in the vector<char>
array.
Each client is represented by the COLLAB::CLIENT
class, containing references to all documents, and a vector<>
of ON
notification classes:
COLLAB::ANYAUTH auth;
COLLAB::CLIENT c1(&auth);
MYON on; c1.AddOn(&on); c1.Connect("localhost",8765);
c1.Open(DOCUMENT_GUID); ...
...
...
c1.Close(DOCUMENT_GUID);
...
...
...
c1.RemoveOn(&on);
c1.Disconnect();
If the document exists, the client is immediately updated from the server (note that, if you have your own updated version of the client, you must push it to the server after this initial update).
When you want to put updates to the server, you call CLIENT::Put()
:
HRESULT Put(GUID g, const char* d,size_t sz);
The function requests the documents signature from the server, then only uploads a diff
to it, minimizing network usage.
The Files
The zip contains:
- A complete collaboration demo solution with prebuilt executable and sources
- diff.h and collab.hpp are all you need to include in your own projects
- Two prebuilt binaries, server.exe and notepad.exe which you can use to create multiple editors of the same file
History
- 20-6-2017: Updated
RWMutex
, tlock
and the library - 1-12-2016: First release