Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Discover COM: Connection Points versus Mailslots in Replication Directory.

0.00/5 (No votes)
19 May 2002 1  
This module is designed to solve the old problem of directory replication.

Sample Image - ReplicationDirectory.gif

Introduction

This module is designed to solve the old problem of directory replication. In fact, we want to put a resource (file or directory) on a server machine and obtain the same structure of directory on the client machine that was advising to the server.

This fact happens automatically and instantly.

Sample Image - NetStructure.gif

How it works:

That module was built out of necessity to automatically and instantaneously reflect the resource uploaded by asp from a server file to other client computers. The server component must be installed on the same computer where there are the asp administration files. The client component must be installed on each web client computer where we want to replicate the server share folder.

The initialization process between server and clients is done automatically after the server and client services start.

It is possible to dynamically add or remove client computers from network, while the system is working. Every time when a new client service will be started on a new computer, the server will be automatically advised about it. If a client will be shut down or stopped for certain reasons, after restarting it, the server will be automatically advised about it.

For making a replication of a resource from server to clients, the only think you can do is to make a call to PutResource method of server component. This must happen after making an asp upload with a new resource (file or directory).

If a total resynchronization of files between server and clients is needed, you have to run the Syncronize method of server object.

In order to see a list of client computers that are advised to server, you have to launch the GetActiveClients method of server object.

Points involved:

  1. Components.
  2. Communication.
  3. Copy files.
  4. Win32 functions.
  5. Installation.
  6. Web calls.

Components.

There are 2 modules involved: a server component and a client (observer) component.

The server component.

The server component is a DCOM exe file component encapsulated in a windows service. This way it is solved the problem of remote calls between computers as well as the problem of keeping in memory the client lists. In addition, a service gives the administrator more facilities, such as stop/run/pause/disable services, and inspects the log service file.

The server component is a multithreading, thread safe system.

Server component methods:

PutResource.( Ex: replication.asp)

It launches a replication of a file or directory. It is made of 3 parameters:

Path = the location path where is the root path of the site (generally, this is �c:\IntePub\wwwroot�). This is obtained with Server.MapPath("/") method.

Directory = the directory name under the path where the asp makes uploads of the resources (ex: �Images�, �CoFiles�). This is the name that will be used to share that directory on server computer.

ResourceName = the name of the file or directory that was uploaded on server under the Path\Directory location. That resource will be instantly copied on the client computers.

Syncronize.( Ex: Resync.asp)

It makes a total resynchronization between server and client files. It doesn't have any parameter.

GetActiveClients.( Ex: PrintClientsList.asp)

It will return a list of client computers that are advised to server. Please, see a demo of how to use it in PrintClientsList.asp. It doesn't have any parameter.

Talk.

It is used by the client services in order to see whether the service is started or not.

PutClientName.

They are used by the client services that tell the server about their names.

The client (observer) component.

The client (observer) component is a DCOM exe file component encapsulated in a windows service. This way it is solved the problem of remote calls between computers as well as the problem of keeping in memory the client lists. In addition, a service gives the administrator more facilities, such as stop/run/pause/disable services, and inspects the log service file.

The client component is a multithreading, thread safe system.

This component service has to run on each computer machine where the directory list from the server is replicated.

Communication.

The server and client services are DCOM components. For that reason, they are simultaneously seen on the network. The server automatically maintains a list of clients. Each client automatically makes an entry point to the server list using the advice ATL function. The server tells all the clients about the existence of a new resource (after the call 'obj.PutResource path, directory, resource') using that list and the connection points ATL system.

That communication task is divided in 2 parts:

DCOM events.

That system is used to send events from the server to all clients who are registered to server via advice function.

The PutResource, GetActiveClients and Syncronizeserver functions fire events who are received by the DCOM clients.

The server connection events class responsible to distribute the specified events is CProxyIServerEvents (Fire_OnNewResource, <code>Fire_OnGetActiveClients, Fire_OnSyncronize). That class and fire functions are builded automatically by the Connection Points wizard.

On the client side the fire events are caught by the CEventHandler class. Here the fallowing macro code distributes the calls from the server to the specified method of client:

BEGIN_SINK_MAP(CEventHandler)
    SINK_ENTRY_EX(0, DIID_IServerEvents, 1, OnNewResource)
    SINK_ENTRY_EX(0, DIID_IServerEvents, 2, OnSyncronize)
    SINK_ENTRY_EX(0, DIID_IServerEvents, 3, OnGetActiveClients)
END_SINK_MAP()

When the client receive the name of the server it launch the <code>LaunchObserver function who advise the server about the existence of the client and create a instance of CEventHandler class. The <code>CoCreateInstanceEx function are used to access the server DCOM service component on the needed machine(m_szServerName), without regarding at the dcomcnfg utility:

COSERVERINFO              psi = { 0, m_szServerName, 0, 0 };
MULTI_QI                  mqi = { &IID_IUnknown, NULL, 0 };
m_pHandler                = new CEventHandler(this);
hr  = CoCreateInstanceEx( CLSID_Server, NULL, CLSCTX_SERVER, &psi, 1, &mqi );
if (FAILED(hr) )          return false;
hr                        = mqi.hr;
if (FAILED(hr))           return false;
m_pUnk                    = (IUnknown*)mqi.pItf;
m_pHandler->DispEventAdvise(m_pUnk);

In the client space are implemented threads who look at equal time intervals at the server if it is down - <code>ComPing. If it is, the client must make readvise on the server to receive events after the restart of the server.

Mailslot calls.

That system is used to distribute the server machine name and server share name to all clients machines who are running in the network.

On the server side, the <code>WorkerThreadSignalClients function launch at equal time interval the <code>CServiceModule::SignalClients member function. The <code>PutSignal function located in Signals.h file retrieve the name of machine server name and write that name to all mail slots opened by the clients on the network.

bool     PutSignal(LPTSTR szMailslotName, LPTSTR &szError)
{
    ...
    if ( !GetComputerName(szMessage, szError) )
         return false;
    if ( !DoMailSlotServerMessageWrite(szMailslotName, szMessage, szError))        
         return false;
    ....
}

This is done in <code>DoMailSlotServerMessageWrite function, using win32 API mailslots functions:

bool DoMailSlotServerMessageWrite ( LPTSTR lpsName, LPTSTR lpsMessage, 
                                    LPTSTR &szError  )
{
	...
	// Create a string for the mailslot name.

	// (all in global domain)

	wsprintf (      achMailSlotName, L"\\\\*\\mailslot\\%s", lpsName );    

	// Open a handle to the mailslot.

	hFile = CreateFile (achMailSlotName,     // file name

		GENERIC_WRITE,                   // access mode

		FILE_SHARE_READ,                 // share mode

		(LPSECURITY_ATTRIBUTES) NULL,    // SD

		OPEN_EXISTING,                   // how to create

		FILE_ATTRIBUTE_NORMAL,           // file attributes

		(HANDLE) NULL );                 // handle to template file

	// Send a mailslot message.

	fResult = WriteFile (    hFile,          // handle to file

		achBuffer,                       // data buffer

		cbToWrite,                       // number of bytes to write

		&cbWritten,                      // number of bytes written

		(LPOVERLAPPED) NULL );           // overlapped buffer

	...
}

On the client side, the <code>WorkerThreadLookupSignals are responsible to launch at equal time intervals the member function <code>CClient::LookupSignals. The <code>ReadSignals uses the <code>GetSignals function located in Signals.h to receive messages on the client mailslot.

bool GetSignals(LPTSTR szMailslotName, LPTSTR &szMessage, LPTSTR &szError)
{
	if ( !DoMailSlot(szMailslotName, hSlot, szError))
		goto err;
	if ( !DoMailSlotMessageRead(szMailslotName, hSlot, szMessage, szError))
		goto err;
	if ( !DoMailSlotClose(hSlot, szError))
		goto err;
}
  • The DoMailSlot function create on the client side a mailslot with name given in lpsName variable. That mailslot are visible on the network using the lpsName name.
    // Create a string for the mailslot name.
    
    wsprintf ( achMailSlotName, L"\\\\.\\mailslot\\%s", lpsName );
    // Create the mailslot.
    
    hSlot = CreateMailslot (    achMailSlotName,    // mailslot name
    
    	0,                                      // maximum message size
    
    	10 * 1000,                              // read time-out interval
    
    	(LPSECURITY_ATTRIBUTES)NULL );          // inheritance option
  • The DoMailSlotMessageRead function read messages in the mailslot created in the DoMailSlot function. That�s messages are wrote from the server component with DoMailSlotServerMessageWrite function.
    fResult = ReadFile (    hSlot,             // handle to file
    
    	achBuffer,                         //data buffer
    
    	cbToRead,                          //number of bytes to read
    
    	&cbRead,                           //number of bytes read
    
    	(LPOVERLAPPED)NULL  );             //overlapped buffer
  • The DoMailSlotClose function close the mailslot opened by DoMailSlot function.
    // Close the handle to our mailslot.
    
    fResult = CloseHandle ( hSlot ); 

Copy files.

The directories and files are effectively copied by client component to local path from server network share. That fact is happening after one event fire by the server component.

To copy that resources from server to client machine is used the extended dos command, xcopy. That command give the possibilities to copy an entire directory or only that files who are newest on the server like on the client machine.

sprintf(szCmdExec, 
"cmd.exe /C xcopy \"%s\" \"%s\" /E /R /K /Y /I /D >>c:\\temp\\LogReplicationClientDos.txt", 
	szServer, szLocal);
RunShell(szCmdExec);

To launch that command are used the win32 CreateProcess function. In CEventHandler class, RunShell method is a wrapper to win32 CreateProcess function:

void     CEventHandler::RunShell(_bstr_t bstrCmdExec)
{
	STARTUPINFO          si;
	PROCESS_INFORMATION  pi; 
	memset(&si,0,sizeof(STARTUPINFO));
	si.cb                = sizeof(STARTUPINFO);
	si.wShowWindow       = SW_SHOW;
	bool bbb             = CreateProcess(NULL, bstrCmdExec, NULL, NULL,
	                                     false, 0, NULL, NULL, &si,&pi);
	CloseHandle(pi.hThread);
	CloseHandle(pi.hProcess);
}

Win32 functions.

Here are 3 points who are resolved by win32 api calls encapsulated in 3 modules. That 3 modules functions are used in CServiceModule::CServiceModule constructor of DCOM service:

Manage user.

Both ReplicationServer and ReplicationClient run under a default local user account (.\IXNET with password ixnet) that is automatically created when installing the services. The module looks whether the user exists, whether the password is ok, etc. If the administrator changes that user (or changes the password) and puts the new one into a registry, the module looks again if the new user is ok. If not, it is changed with the default one.

That�s functionalities are encapsulated in AccountMannager.h file.

  • The gethostname function returns the standard host name for the local machine.
    bool GetComputerNameLocal( LPTSTR &szName, LPTSTR &szError )
    
  • The GetComputerNameEx function retrieves a NetBIOS or DNS name associated with the local computer.
    bool GetDomainNameLocal( LPTSTR &szName, LPTSTR &szError )
    
  • The AccountCreate function use the NetUserAdd win api function to add a user account and assigns a password and privilege level:
    bool AccountCreate( LPTSTR szAccountDefault, LPTSTR szDomainName, 
                        LPTSTR szAccountPassword, LPTSTR &szError )
    
  • The AccountInsertInGroup function use the NetLocalGroupAddMembers function to add membership of one or more existing user accounts or global group accounts to an existing local group:
    bool AccountInsertInGroup( LPTSTR szAccountDefault, LPTSTR szLocalGroup, 
                               LPTSTR &szError )
    
  • The AccountExist function use the NetUserGetInfo function to retrieve information about a particular user account on a server.
    bool AccountExist( LPTSTR szAccountDefault, bool &bExist, LPTSTR &szError )
    
  • The <code>AccountDelete function use the NetUserDel function to delete a user account from a server.
    bool AccountDelete( LPTSTR szAccountDefault, LPTSTR &szError )
    

Manage service credentials.

When a service runs under user credential rights, it must have these rights. This is done only first time when the service is started. The module looks at the user assigned to the service, looks if it is ok and adds the launch service rights to that user.

That�s functionalities are encapsulated in ServiceRights.h file. This module are a version of the basic win32 program privs.exe.

  • The OpenPolicy function use the The LsaOpenPolicy function opens a handle to the Policy object on a local or remote system. To administer the local security policy of a local or remote system, you must call the LsaOpenPolicy function to establish a session with that system's LSA subsystem.
    NTSTATUS OpenPolicy( LPWSTR ServerName, DWORD DesiredAccess, 
                         PLSA_HANDLE PolicyHandle)
    
  • The GetAccountSid function obtain the SID of the user/group. It used the LookupAccountName function who retrieves a security identifier (SID) for the account and the name of the domain on which the account was found.
    BOOL GetAccountSid( LPTSTR SystemName, LPTSTR AccountName, PSID *Sid)
    
  • The SetPrivilegeOnAccount function grant the SeServiceLogonRight to users represented by pSid. It uses the win api LsaAddAccountRights function assigns one or more privileges to an account. If the account does not exist, LsaAddAccountRights creates it.
    NTSTATUS SetPrivilegeOnAccount( LSA_HANDLE PolicyHandle, PSID AccountSid, 
                                    LPWSTR PrivilegeName, BOOL bEnable)
    

Manage share.

In order to copy the files between server computer and client computers, a network share of server repository files must be done. This is done automatically when the server ReplicationService calls the first time the PutResource method call.

That�s functionalities are encapsulated in ShareMannager.h file.

  • The GetAccess function get security descriptor to AccountName. It use the LookupAccountName function to retrieves a security identifier (SID) for the account and the name of the domain on which the account was found. After that the InitializeAcl function creates a new ACL structure and The AddAccessAllowedAce function is used to add an access-allowed ACE to that ACL. Finally are used the InitializeSecurityDescriptor and SetSecurityDescriptorDacl functions to set information in that discretionary access-control list (ACL).
    bool GetAccess( LPTSTR AccountName, SECURITY_DESCRIPTOR &sd, 
                    LPTSTR &szError )
    
  • The ShareCreate function use the NetShareAdd win32 api function shares a server resource.
    bool ShareCreate( LPTSTR pszDirectoryToShare, LPTSTR pszShareName, 
                      SECURITY_DESCRIPTOR sd, LPTSTR &szError )
    
  • The DeleteShare function use the NetShareDel function to delete a share name from a server's list of shared resources.
    bool DeleteShare(LPTSTR pszPathRoot, LPTSTR pszDirectory, 
                     LPTSTR &szError)
    

Installation.

Server service component installation.

  • Copy the �ReplicationServer.exe� file into a directory from the server computer (a component directory, with enough rights).

  • Register ReplicationServer like a service running the command
    ReplicationServer.exe /service

    in msdos window.

  • If everything is ok, in the services control panel will appear a new service -> �ReplicationServer�, running under the .\IXNET local user account.

  • Run the service by clicking on the �Start Service� button on the services control panel.

Client service component installation.

  • Edit the registry and put/edit the path where the structure of server directory will be replicated. The default is: [HKEY_LOCAL_MACHINE\SOFTWARE\Articles\Replication Directory\Paths] with key: "COFilesPath"="C:\\Temp\\COFiles\\".
  • Copy the �ReplicationServer.exe� file into a directory from the server computer (a component directory, with enough rights).

  • Copy the �ReplicationClient.exe� file into the same directory.

  • In order to run ReplicationClient, you need to find some information about the server component. This is done by registering the server component on each client computer: run in msdos window the command
    ReplicationServer.exe /regserver

    Attention: the ReplicationServer must run like a service only to the server computer!!!

  • Register ReplicationClient like a service running the command
    ReplicationClient.exe /service

    in msdos window.

  • If everything is ok, in the services control panel will appear a new service -> �ReplicationClient�, running under the .\IXNET local user account.

  • Run the service by clicking on the �Start Service� button on the services control panel.

Modification of user account.

Both ReplicationServer and ReplicationClient run under a default local user account (.\IXNET with password ixnet) that is automatically created when installing the services. If you need to change it or to put a domain user, edit the registry key.

[HKEY_LOCAL_MACHINE\SOFTWARE\Articles\Replication Directory\Admin]

It is necessary that the user to give credential rights to services in order to copy the files within the network.

Start replication system.

After the successful installation of ReplicationServer and ReplicationClients services, the system will wait the first call of PutResource(Ex: replication.asp) , to make the initialization of system. After that, the first call of the method containing the parameter "Directory" will create on the server computer a new share with the same name "Directory". All clients will be advised about that share. For security reasons, only the ".\IXNET" users have rights on that share

Web calls.

The idea of this system is to replicate immediately to another computers a file or an entire directory structure that is uploaded by asp on the repository server. In order to do this, just make a call to PutResource method after a successful upload. Anyway, it is possible that the system to work independently in asp, with calls made by other modules: SQL, C++, VB, Delphi.

Replication.asp.

This asp file gives a sample about how to publish a new resource using the replication system.

'create an instance of our server service object

set obj = server.CreateObject("ReplicationServer.Server.1")  

'this are the share repository directory

directory = "CoFiles"                                        

'this are the local path to the share repository directory

path = Server.MapPath("/")                                   

obj.PutResource path, directory, "atl"

'"atl" must be a name of a file or directory located in: path\ImagesDirectory\

'The parameter of PutResource method :RootPath, ImagesDirectory, ResourceFileName.

Resync.asp .

This asp file gives a sample about how to make a total resynchronization of files between server and client computers. All the files that are new on the server share and aren't on client computer will be copied.

'create an instance of our server

set obj = server.CreateObject("ReplicationServer.Server.1")

'launch synchronize method service object

obj.Syncronize 

PrintClientsList.asp.

This asp file gives a sample about how to obtain a list of current client computers that are running the ReplicationClient service and that are online.

'create an instance of our server service object

set obj = server.CreateObject("ReplicationServer.Server.1") 

str = obj.GetActiveClients   'launch GetActiveClients method

Response.Write str           'write the list

Observations:

  • The ReplicationServer and ReplicationClient services must run on different computers.

  • On the computer where are running one of that services must be created the directory C:\temp, where the system put the log files: Log.txt and LogDos.txt.

  • Some settings are kept between session in the registry using the WriteSettingsPaths and ReadSettingsPaths functions. These settings are automatically obtained by components. The only setting who are needed to be writted in registry by the hand is \Paths\CoFilesPath - the local location of replication directory, on each client machine.
  • To compile the projects must have the path �C:\Program Files\Microsoft SDK\include� and �C:\Program Files\Microsoft SDK\lib� on the first position on the Options->Directories Include and Library files.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here