Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Real Time Collaboration: A Quick C++ Windows Library with RDC Support

5.00/5 (17 votes)
14 Feb 2019CPOL4 min read 20.1K   212  
Create collaborating projects easily

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.

C++
//
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
//

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:

C++
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:

C++
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:

C++
//
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();

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():

C++
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)