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

发现COM:连接点与邮件槽在复制目录中的区别。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (6投票s)

2002年5月8日

12分钟阅读

viewsIcon

75446

downloadIcon

1049

该模块旨在解决目录复制的旧问题。

Sample Image - ReplicationDirectory.gif

引言

该模块旨在解决目录复制的旧问题。事实上,我们希望将一个资源(文件或目录)放置在服务器计算机上,并在客户端计算机上获得与向服务器发出通知的目录相同的目录结构。

这一事实会自动且即时地发生。

Sample Image - NetStructure.gif

工作原理

该模块的诞生源于需要自动且即时地将ASP上传的资源从服务器文件反映到其他客户端计算机。服务器组件必须安装在与ASP管理文件相同的计算机上。客户端组件必须安装在我们希望复制服务器共享文件夹的每个Web客户端计算机上。

服务器和客户端之间的初始化过程在服务器和客户端服务启动后自动完成。

可以在系统运行时动态地添加或删除网络上的客户端计算机。每当新客户端服务在新计算机上启动时,服务器都会自动收到通知。如果客户端因某些原因关闭或停止,在重新启动后,服务器会自动收到通知。

为了将资源从服务器复制到客户端,您唯一能做的就是调用服务器组件的PutResource方法。这必须在通过ASP上传新资源(文件或目录)后进行。

如果需要对服务器和客户端之间的文件进行完全重新同步,则必须运行服务器对象的Syncronize方法。

为了查看已通知服务器的客户端计算机列表,您必须调用服务器对象的GetActiveClients方法。

涉及要点

  1. 组件。
  2. 通信。
  3. 复制文件。
  4. Win32函数。
  5. 安装。
  6. Web调用。

组件。

涉及2个模块:服务器组件和客户端(观察者)组件。

服务器组件。

服务器组件是一个封装在Windows服务中的DCOM可执行文件组件。这样就解决了计算机之间的远程调用问题,以及保持客户端列表在内存中的问题。此外,服务为管理员提供了更多便利,例如停止/运行/暂停/禁用服务,并检查日志服务文件。

服务器组件是一个多线程、线程安全系统。

服务器组件方法

PutResource。(例如:replication.asp)

它启动文件或目录的复制。它包含3个参数:

Path = 网站根目录的路径位置(通常是“c:\IntePub\wwwroot”)。这是使用Server.MapPath(“/”)方法获得的。

Directory = ASP上传资源的目录名称(例如,“Images”,“CoFiles”)。这将是服务器计算机上共享该目录的名称。

ResourceName = 在Path\Directory位置上传到服务器的文件或目录的名称。该资源将立即复制到客户端计算机。

Syncronize。(例如:Resync.asp)

它对服务器和客户端文件进行完全重新同步。它没有任何参数。

GetActiveClients。(例如:PrintClientsList.asp)

它将返回一个已通知服务器的客户端计算机列表。请参阅PrintClientsList.asp中的使用示例。它没有任何参数。

Talk。

客户端服务使用它来查看服务是否已启动。

PutClientName。

客户端服务使用它们将自己的名称告知服务器。

客户端(观察者)组件。

客户端(观察者)组件是一个封装在Windows服务中的DCOM可执行文件组件。这样就解决了计算机之间的远程调用问题,以及保持客户端列表在内存中的问题。此外,服务为管理员提供了更多便利,例如停止/运行/暂停/禁用服务,并检查日志服务文件。

客户端组件是一个多线程、线程安全系统。

该组件服务必须在从中复制服务器目录列表的每台计算机上运行。

通信。

服务器和客户端服务是DCOM组件。因此,它们可以在网络上同时可见。服务器会自动维护一个客户端列表。每个客户端都使用ATL的建议函数自动将自己添加到服务器列表。服务器使用该列表和ATL的连接点系统,将新资源的存在通知所有客户端(在调用'obj.PutResource path, directory, resource'之后)。

该通信任务分为2部分:

DCOM事件。

该系统用于将事件从服务器发送“到所有已通过建议函数注册到服务器的客户端。”

PutResourceGetActiveClientsSyncronizeserver函数触发事件,这些事件由DCOM客户端接收。

负责分发指定事件的服务器连接事件类是CProxyIServerEvents(Fire_OnNewResourceFire_OnGetActiveClientsFire_OnSyncronize)。该类和fire函数由连接点向导自动构建。

在客户端,fire事件由CEventHandler类捕获。在这里,以下宏代码将来自服务器的调用分发到客户端的指定方法。

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

当客户端接收到服务器名称时,它调用LaunchObserver函数,该函数将客户端的存在通知服务器并创建一个CEventHandler类的实例。CoCreateInstanceEx函数用于访问所需计算机上的服务器DCOM服务组件(m_szServerName),而无需考虑dcomcnfg实用程序。

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);

在客户端空间实现了线程,这些线程以相等的时间间隔检查服务器是否已关闭 - ComPing。如果是,客户端必须重新建议服务器,以便在服务器重新启动后接收事件。

邮件槽调用。

该系统用于将服务器计算机名称和服务器共享名称分发到网络上运行的所有客户端计算机。

在服务器端,WorkerThreadSignalClients函数以相等的时间间隔调用CServiceModule::SignalClients成员函数。位于Signals.h文件中的PutSignal函数检索服务器计算机名称,并将该名称写入客户端在网络上打开的所有邮件槽。

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

这是在DoMailSlotServerMessageWrite函数中完成的,使用win32 API邮件槽函数。

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
	...
}

在客户端,WorkerThreadLookupSignals负责以相等的时间间隔调用成员函数CClient::LookupSignalsReadSignals使用Signals.h中找到的GetSignals函数来接收客户端邮件槽中的消息。

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;
}
  • DoMailSlot函数在客户端创建名称为lpsName变量的邮件槽。该邮件槽在网络上可见,名称为lpsName。
    // 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
  • DoMailSlotMessageRead函数读取DoMailSlot函数创建的邮件槽中的消息。这些消息由DoMailSlotServerMessageWrite函数从服务器组件写入。
    fResult = ReadFile (    hSlot,             // handle to file
    	achBuffer,                         //data buffer
    	cbToRead,                          //number of bytes to read
    	&cbRead,                           //number of bytes read
    	(LPOVERLAPPED)NULL  );             //overlapped buffer
  • DoMailSlotClose函数关闭DoMailSlot函数打开的邮件槽。
    // Close the handle to our mailslot.
    fResult = CloseHandle ( hSlot ); 

复制文件。

目录和文件实际上由客户端组件从服务器网络共享复制到本地路径。这一事实发生在服务器组件触发一个事件之后。

为了将这些资源从服务器复制到客户端计算机,使用了扩展的dos命令xcopy。该命令提供了复制整个目录或仅复制服务器上比客户端更新的文件(反之亦然)的可能性。

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

使用win32 CreateProcess函数启动该命令。在CEventHandler类中,RunShell方法是win32 CreateProcess函数的包装器。

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函数。

这里有3个要点,它们由封装在3个模块中的win32 API调用解决。这3个模块的函数在DCOM服务的CServiceModule::CServiceModule构造函数中使用。

管理用户。

ReplicationServer和ReplicationClient都运行在一个默认的本地用户帐户(.\IXNET,密码为ixnet)下,该帐户在安装服务时自动创建。该模块检查用户是否存在,密码是否正确等。如果管理员更改了该用户(或更改了密码)并将其放入注册表,该模块会再次检查新用户是否正确。如果不正确,则会更改为默认用户。

这些功能封装在AccountMannager.h文件中。

  • gethostname函数返回本地计算机的标准主机名。
    bool GetComputerNameLocal( LPTSTR &szName, LPTSTR &szError )
    
  • GetComputerNameEx函数检索与本地计算机关联的NetBIOS或DNS名称。
    bool GetDomainNameLocal( LPTSTR &szName, LPTSTR &szError )
    
  • AccountCreate函数使用NetUserAdd win api函数添加用户帐户并分配密码和特权级别。
    bool AccountCreate( LPTSTR szAccountDefault, LPTSTR szDomainName, 
                        LPTSTR szAccountPassword, LPTSTR &szError )
    
  • AccountInsertInGroup函数使用NetLocalGroupAddMembers函数将一个或多个现有用户帐户或全局组帐户的成员身份添加到现有本地组。
    bool AccountInsertInGroup( LPTSTR szAccountDefault, LPTSTR szLocalGroup, 
                               LPTSTR &szError )
    
  • AccountExist函数使用NetUserGetInfo函数检索服务器上特定用户帐户的信息。
    bool AccountExist( LPTSTR szAccountDefault, bool &bExist, LPTSTR &szError )
    
  • AccountDelete函数使用NetUserDel函数从服务器删除用户帐户。
    bool AccountDelete( LPTSTR szAccountDefault, LPTSTR &szError )
    

管理服务凭据。

当服务以用户凭据权限运行时,它必须具有这些权限。这仅在服务首次启动时完成。该模块会查看分配给服务的用户,检查其是否正确,并向该用户添加启动服务权限。

这些功能封装在ServiceRights.h文件中。该模块是基本win32程序privs.exe的一个版本。

  • OpenPolicy函数使用LsaOpenPolicy函数打开本地或远程系统上策略对象的句柄。要管理本地或远程系统的本地安全策略,必须调用LsaOpenPolicy函数以与该系统的LSA子系统建立会话。
    NTSTATUS OpenPolicy( LPWSTR ServerName, DWORD DesiredAccess, 
                         PLSA_HANDLE PolicyHandle)
    
  • GetAccountSid函数获取用户/组的SID。它使用LookupAccountName函数来检索帐户的安全标识符(SID)以及找到帐户的域名称。
    BOOL GetAccountSid( LPTSTR SystemName, LPTSTR AccountName, PSID *Sid)
    
  • SetPrivilegeOnAccount函数授予由pSid表示的用户SeServiceLogonRight。它使用win api LsaAddAccountRights函数将一个或多个特权分配给帐户。如果帐户不存在,LsaAddAccountRights会创建它。
    NTSTATUS SetPrivilegeOnAccount( LSA_HANDLE PolicyHandle, PSID AccountSid, 
                                    LPWSTR PrivilegeName, BOOL bEnable)
    

管理共享。

为了在服务器计算机和客户端计算机之间复制文件,必须进行服务器存储库文件的网络共享。这在服务器ReplicationService首次调用PutResource方法调用时自动完成。

这些功能封装在ShareMannager.h文件中。

  • GetAccess函数获取AccountName的安全描述符。它使用LookupAccountName函数来检索帐户的安全标识符(SID)以及找到帐户的域名称。之后,InitializeAcl函数创建一个新的ACL结构,并且AddAccessAllowedAce函数用于向该ACL添加一个允许访问的ACE。最后,使用InitializeSecurityDescriptorSetSecurityDescriptorDacl函数在该自主访问控制列表(ACL)中设置信息。
    bool GetAccess( LPTSTR AccountName, SECURITY_DESCRIPTOR &sd, 
                    LPTSTR &szError )
    
  • ShareCreate函数使用NetShareAdd win32 api函数共享服务器资源。
    bool ShareCreate( LPTSTR pszDirectoryToShare, LPTSTR pszShareName, 
                      SECURITY_DESCRIPTOR sd, LPTSTR &szError )
    
  • DeleteShare函数使用NetShareDel函数从服务器的共享资源列表中删除共享名称。
    bool DeleteShare(LPTSTR pszPathRoot, LPTSTR pszDirectory, 
                     LPTSTR &szError)
    

安装。

服务器服务组件安装。

  • 将“ReplicationServer.exe”文件复制到服务器计算机上的一个目录中(一个具有足够权限的组件目录)。

  • 将ReplicationServer注册为服务,运行以下命令:
    ReplicationServer.exe /service

    在MSDOS窗口中。

  • 如果一切正常,在服务控制面板中将出现一个新服务 -> “ReplicationServer”,运行在.\IXNET本地用户帐户下。

  • 通过在服务控制面板中单击“启动服务”按钮来运行服务。

客户端服务组件安装。

  • 编辑注册表,并设置/编辑将复制服务器目录结构的路径。默认值为:[HKEY_LOCAL_MACHINE\SOFTWARE\Articles\Replication Directory\Paths] 键为:"COFilesPath"="C:\\Temp\\COFiles\\"
  • 将“ReplicationServer.exe”文件复制到服务器计算机上的一个目录中(一个具有足够权限的组件目录)。

  • 将“ReplicationClient.exe”文件复制到同一目录。

  • 为了运行ReplicationClient,您需要找到关于服务器组件的一些信息。这是通过在每个客户端计算机上注册服务器组件来完成的:在MSDOS窗口中运行命令:
    ReplicationServer.exe /regserver

    注意:ReplicationServer只能在服务器计算机上作为服务运行!!!

  • 将ReplicationClient注册为服务,运行以下命令:
    ReplicationClient.exe /service

    在MSDOS窗口中。

  • 如果一切正常,在服务控制面板中将出现一个新服务 -> “ReplicationClient”,运行在.\IXNET本地用户帐户下。

  • 通过在服务控制面板中单击“启动服务”按钮来运行服务。

修改用户帐户。

ReplicationServer和ReplicationClient都运行在一个默认的本地用户帐户(.\IXNET,密码为ixnet)下,该帐户在安装服务时自动创建。如果您需要更改它或设置一个域用户,请编辑注册表项。

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

服务需要用户授予凭据权限才能在网络上复制文件。

启动复制系统。

在ReplicationServer和ReplicationClients服务成功安装后,系统将等待第一个PutResource(例如:replication.asp)调用,以初始化系统。之后,第一个包含“Directory”参数的方法调用将在服务器计算机上创建一个同名的“Directory”共享。所有客户端都将被通知该共享。出于安全原因,只有“.\IXNET”用户对此共享拥有权限。

Web调用。

此系统的想法是立即将ASP在存储库服务器上上传的文件或整个目录结构复制到其他计算机。为此,只需在成功上传后调用PutResource方法即可。无论如何,系统都可以独立于ASP工作,通过其他模块(SQL、C++、VB、Delphi)的调用。

Replication.asp。

此ASP文件提供了如何使用复制系统发布新资源的示例。

'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”必须是位于:path\ImagesDirectory\ 的文件或目录的名称。

PutResource方法的参数:RootPath, ImagesDirectory, ResourceFileName。

Resync.asp。

此ASP文件提供了一个如何进行服务器和客户端计算机之间文件完全重新同步的示例。所有服务器共享上新但客户端上没有的文件都将被复制。

'create an instance of our server
set obj = server.CreateObject("ReplicationServer.Server.1")

'launch synchronize method service object
obj.Syncronize 

PrintClientsList.asp。

此ASP文件提供了一个如何获取正在运行ReplicationClient服务且在线的当前客户端计算机列表的示例。

'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

观察

  • ReplicationServer和ReplicationClient服务必须运行在不同的计算机上。

  • 运行其中一个服务的计算机上必须创建C:\temp目录,系统将日志文件Log.txt和LogDos.txt放在其中。

  • 一些设置使用WriteSettingsPathsReadSettingsPaths函数在注册表中跨会话保留。这些设置由组件自动获取。唯一需要手动写入注册表设置的是\Paths\CoFilesPath - 每台客户端计算机上复制目录的本地位置。
  • 要编译项目,必须将“C:\Program Files\Microsoft SDK\include”和“C:\Program Files\Microsoft SDK\lib”路径放在Options->Directories Include和Library files的第一个位置。

© . All rights reserved.