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

双向 RPC

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.12/5 (17投票s)

2003年5月5日

3分钟阅读

viewsIcon

103781

downloadIcon

3333

如何使用 RPC 实现双向客户端/服务器过程调用。

Sample Image - birpc.jpg

引言

RPC 通常不直接使用。因此,当您使用 DCOM 在 www 上系统之间进行通信时,通常不必了解 RPC API。 但是 - 正如您稍后将看到的 - 有时未覆盖的 RPC 非常有用。

例如:在传统的 RPC 编程中,您有两个软件,每个软件都采用可区分的可执行形式。

app A : the client - send request to be served.
app B : the server - complies to the client request.

大多数情况下,这很好而且令人满意。 但是,为了实现这个目标 - 您不需要直接使用 RPC - 分布式 COM 已经足够了。

那么,什么时候才真正需要 RPC?

好吧,假设一个可执行文件交替扮演客户端或服务器的角色。 除此之外,假设客户端角色和服务器角色使用相同的接口和相同的功能。

困惑了吗?

不要!!!

解决方案

  1. 试用下载的演示项目,并仔细扫描源代码以获取更多信息...
  2. 在进行大试用之前,请阅读以下几行

解释

我假设您已经知道如何通过 MIDL 编译器生成 RPC 代码,因此我将仅解释需要进行的更改

  1. RPC 代码中的一些变量必须替换为相似的变量,这样就不会有重复的变量,因为客户端代码和服务器代码对三个变量使用相似的名称。
     __MIDL_ProcFormatString replaced by __MIDL_ProcFormatString2
     
     __MIDL_TypeFormatString replaced by  __MIDL_TypeFormatString2
       
     <interface_name>_StubDesc replaced by <interface_name>_StubDesc2

    上面的更改应该只在源代码的客户端进行。

    我们不能对客户端和服务器端使用这些变量的一个实例,因为在每种情况下,这些变量都有不同的值;

  2. 将所有需要参与 RPC 故事的文件添加到一个项目中,而不是像通常实现 RPC 那样 - 在两个不同的项目中。
  3. 此外,更改 RPC 故事中涉及的函数的名称
      <func-name>(<parameters>) to <func-name>2(<parameters>).

    这是演示项目中的一段代码。 服务器端接口实现了 GetName 函数。 因为客户端和服务器端都实现了以下功能

       void GetName( 
        /* [size_is][string][out] */ unsigned char __RPC_FAR name[  ])
       {
        ....
       }
       

    ...并且因为我们将客户端代码和服务器代码一起编译,所以我们必须更改服务器端中的 func-name 以避免重复,如下所示

       void GetName2( 
        /* [size_is][string][out] */ unsigned char __RPC_FAR name[  ])
       {
        ....
       }

    这是 MIDL 生成的函数 - 唯一的变化是内部函数 GetName 的名称(纯实现函数 - stub naked)。

    void __RPC_STUB
    details_GetName(
        PRPC_MESSAGE _pRpcMessage )
    {
        MIDL_STUB_MESSAGE _StubMsg;
        unsigned char ( __RPC_FAR *name )[  ];
        RPC_STATUS _Status;
        
        ((void)(_Status));
        NdrServerInitializeNew(
                              _pRpcMessage,
                              &_StubMsg,
                              &details_StubDesc);
        
        name = 0;
        RpcTryFinally
            {
            RpcTryExcept
                {
                if(_StubMsg.Buffer > _StubMsg.BufferEnd)
                    {
                    RpcRaiseException(RPC_X_BAD_STUB_DATA);
                    }
                }
            RpcExcept( RPC_BAD_STUB_DATA_EXCEPTION_FILTER )
                {
                RpcRaiseException(RPC_X_BAD_STUB_DATA);
                }
            RpcEndExcept
            if(100 * 1 < 0)
                {
                RpcRaiseException(RPC_X_INVALID_BOUND);
                }
            name = (unsigned char (*)[])NdrAllocate(&_StubMsg,100 * 1);
            
            // GetName(*name) is the original source line produced
            // by midl compiler.
            GetName2(*name); 
                             
            
            _StubMsg.BufferLength = 0U;
            _StubMsg.MaxCount = 100;
            
            NdrConformantStringBufferSize( 
                 (PMIDL_STUB_MESSAGE) &_StubMsg,
                 (unsigned char __RPC_FAR *)*name,
                 (PFORMAT_STRING) &__MIDL_TypeFormatString.Format[2] );
            
            _pRpcMessage->BufferLength = _StubMsg.BufferLength;
            
            _Status = I_RpcGetBuffer( _pRpcMessage ); 
            if ( _Status )
                RpcRaiseException( _Status );
            
            _StubMsg.Buffer = 
              (unsigned char __RPC_FAR *) _pRpcMessage->Buffer;
            
            _StubMsg.MaxCount = 100;
            
            NdrConformantStringMarshall( 
                 (PMIDL_STUB_MESSAGE)& _StubMsg,
                 (unsigned char __RPC_FAR *)*name,
                 (PFORMAT_STRING) &__MIDL_TypeFormatString.Format[2] );
            
            }
        RpcFinally
            {
            if ( name )
                _StubMsg.pfnFree( name );
            
            }
        RpcEndFinally
        _pRpcMessage->BufferLength = 
            (unsigned int)((long)_StubMsg.Buffer - 
            (long)_pRpcMessage->Buffer);
        
    }

练习

要测试演示项目,请启动两个 GETNAME 应用程序副本。 在任何副本中输入两个不同的端点,并针对您敌人的名字(或您的妻子 - 同上)输入您的名字。

         end point of    end point of     your name
         remote server   local server
        ---------------  -------------    ------------

app A -   55459            55460            moshe     

app B -   55460            55459            shoshana

在两个副本上单击PlayServer按钮。 正如您所看到的,每个应用程序都指向另一个应用程序,因此通信是双向的。 因此,每个应用程序都是客户端和服务器,具有相同的请求(即 - 从远程应用程序获取名称)。

此演示中的 IP 由“localhost”字符串表示,指向由“localhost”表示的本地计算机。

在应用程序的每个副本中单击“从远程服务器获取名称”按钮,您将获得对方应用程序用户的姓名。

好极了!!!

兼容性说明

该演示项目在 Windows 98 和 Windows 2000 中运行良好。

澄清

为了简化并突出本文的主题,我没有使用一些必要的代码(RPC 异常、线程状态检查活动从摇篮到伊甸园以及其他有用的状态处理)。

© . All rights reserved.