您的位置:首页 > 理论基础 > 计算机网络

如何实现 RPC 框架的要点浅析

2017-02-24 09:45 441 查看
================================================================================

标题: 如何实现 RPC 框架的要点浅析

作者: 叶飞虎

日期: 2017.02.23

--------------------------------------------------------------------------------

1. 什么是 RPC?

    RPC 就是 Remote Procedure Call 三个单词首字母的缩写,其字面意思为:远程过程

调用。而远程过程调用直观说法就是:进程 A 远程调用进程 B 的过程方法。既然是远程调

用则需要涉及网络传输,由此可见 RPC 主要应用于不同主机间的过程调用,也可用于本机

中不同进程之前的过程调用。

    简单的说,RPC 就是从一台主机上的进程 A 通过参数传递的方式调用另一台主机上的

进程 B 中的一个函数,并得到返回的结果。因此 RPC 具有以下特点:

    a. RPC 会隐藏底层的通讯细节,不需要直接处理如何通信及收发数据。

    b. RPC 是一个请求响应模型。客户端发起调用请求,服务端返回请求响应,这类似于

       HTTP 的工作方式。

    c. RPC 在使用形式上像调用本地函数一样去调用远程的函数。

2. 为什么要用 RPC?

    在以前的单机时代,一台主机上可以运行多个进程,假如进程 A 和 B 都要控制打印机

进行打印,而打印机同时只能被一个进程控制操作,这要怎么办才好呢?为了解决这一问题

系统提供了打印服务 C 进程,C 进程有打印任务队列,A 和 B 进程把需要打印生成一个打

印任务,然后向 C 进程提交请求即可。A 和 B 进程与 C 进程之间通信就是 IPC,IPC 就

是 Inter-Process Communication 缩写,其主要功能就是单机中进程之间的相互通信。

    到了网络时代,不可能每台主机都配置一台打印机,不同主机需要访问带打印机的主机

时 IPC 已无能为力。这时就需要把 IPC 扩展到网络上,而网络通信有很多通讯协议,如:

TCP、UDP等等。不同通讯协议的控制方式不同,这给开发人员带来困难,只要不同主机之间

涉及网络通信,其需要考虑的因素就非常多了,如:网速、断线、延时、私有性、安全性等

等。于是 RPC 就是为简化远程调用应运而生了,开发人员使用 RPC 时无须考虑如何握手连

接、如何收发数据等等问题。

    什么时候要用到 RPC 呢?就是无法在单个进程内,甚至单个主机内通过本地调用的方

式完成的需求,比如不同的系统之间的通讯,甚至不同的组织间的通讯。例如计算能力需要

横向扩展,需要在多台主机组成的集群上部署应用,同时要简化通讯细节,这时 RPC 框架

就发挥作用了。集群部署的本质就是分布式服务,由此可见 RPC 框架是分布式服务的基石,

实现 RPC 框架需要考虑方方面面。其对业务隐藏了底层通信过程: TCP/UDP通讯协议、函数

参数打包/解包、参数数据序列化/反序列化,使开发人员专注于功能实现。

3. RPC 框架分层

    我们可以对 RPC 框架进行抽丝剥茧,并从层次上把 RPC 分为四层:RPC 物理通讯层、

RPC 通讯协议层、RPC 连接会话层、RPC 应用层。

    a. RPC 物理通讯层,根据实际用途可以选择合适的实际物理通讯协议:

       1). 可以使用 TCP 网络通讯协议,直接调用套接字 socket 接口函数或者 IOCP 接

           口函数进行封装;

       2). 可以使用 UDP 网络通讯协议,但必须自己来维护数据包的时序、丢包重传、以

           及虚拟连接状态。虽然使用 UDP 相对比较麻烦一点,但是 UDP 也有很多优点,

           如:NAT 穿透相对容易、每包数据完整等等;

       3). 可以使用 SHM 共享内存通信,用于同一主机不同进程之间的 RPC 调用,优点

           是传输速度快,缺点是只能在同一主机上运行;

       4). 可以使用第三方通讯模块接口,如:蓝牙、串口、USB连线接口等等。优点是可

           以跨终端设备进行相互调用、缺点是受制于第三方通讯模块接口。

    b. RPC 通讯协议层就是通讯双方的消息包定义,以及每种消息的数据组织,只需要负

       责收发的数据流,具体如何收发数据由物理通讯层去处理。

       1). 消息包 = <消息头> + [消息体],也就是说由消息头和消息体组成一个消息包;

       2). 消息头必须含有:消息包ID、消息类型、消息体长度、函数ID和调用返回码。

           消息头若要做得更完善还可以添加检验码等等,但消息头所占尺寸不宜太大,

           否则 RPC 的调用开销就更大了。

       3). 消息类型至少含有以下几种:

           a). 心跳,含请求和应答;

           b). 连接握手,含请求和应答;

           c). 登录应用,含请求和应答;

           d). 登出应用,含请求和应答;

           e). 取定义集,含请求和应答,即读取已登录应用的函数定义集;

           f). 调用函数,含请求和应答;

           g). 函数取消,即取消已提交的调用函数请求;

           h). 函数确认,即函数返回结果确认;

           i). 发送数据,用于扩展 RPC 为数据包通讯。

    c. RPC 连接会话层,负责通讯双方的连接状态管理,包括以下几个方面:

       1). 通过连接握手来确认会话双方的合法性;

       2). 通过心跳维护连接在线情况;

       3). 通过登录应用来获取对方应用提供的函数定义集,只有知道函数的参数和返回

           值定义才可以调用函数,函数定义是参数和返回值的序列化/反序列化前提;

       4). 通过登出应用来关闭对端提供的函数调用;

       5). 管理函数调用的整个生命周期;

       6). 管理发送数据包缓冲队列、以及接收到数据包的处理。

    d. RPC 应用层,负责应用内的函数定义、管理连接的登录登出、分配函数调用请求。

       1). 在函数定义中,函数参数和返回值的数据类型要支持足够多,不同数据类型可

           以便于数据展示,也增加了 RPC 框架的灵活性。同时被 RPC 调用的函数回调

           类型要支持多种调用方式,可以适用于更多开发语言。

       2). 在连接登录到应用后才可以接受函数调用请求,当连接登出应用后自动取消此

           连接的所有函数调用请求;

       3). 应用需要管理运行线程,用于执行已分配的函数调用请求。

4. RPC 实现及要点分析

    讲述 RPC 要点分析之前,先讲述一下我所理解并已实现的通用 RPC 框架,并从已实现

的不同版本 RPC 框架中分析其差异,并从中一窥通用 RPC 框架要点和难点。

    在 2006 年本人就实现了跨语言平台的 RPC 框架,当时的 RPC 框架命名为 GCI, GCI

就是 General Communication Interface 缩写。从 GCI 1.0 和 2.0,到后来改名 RC 3.0

(RC 就是 Remote Call 缩写),都是跨操作系统及跨开发语言的通用框架。在 Windows 下

以动态库形式提供调用接口,而在 Linux 下则以共享库形式提供调用接口。

    RC下载地址:http://pan.baidu.com/s/130O9o

    a. RC 3.0 及以前版本中,RPC 框架接口分为二个部分:Client 和 Server 端接口。

       1). Client 端负责函数的参数打包和请求发送,以及等待应答返回值的解包和分发;

       2). Server 端负责接收 Client 端的函数请求,请求的调度并应答返回。

    b. RC 3.0 的 Client 端由三大类组成:Connection 类、Command 类和 Fields 类,

       其主要功能如下:

       1). Connection 类主要负责与 Server 端的通讯和应答返回值的分发;

       2). Command 类主要负责命令参数编辑,提交及返回值读取操作;

       3). Command 执行请求可以阻塞等待应答返回值,也可以非阻塞立即返回,通过连

           接 Connection 分发获取应答返回值;

       4). Connection 分发分为回调方式和查询方式,二者在一个 Connection 对象中只

           能属于其中一种方式,而查询方式还可以分为阻塞和非阻塞读取;

       5). Fields 类主要负责字段集值和字段定义集的组包和拆包。

    c. RC 3.0 的 Server 端由五大类组成:Server 类、Application 类、Function 类、

       Response 类和 Fields 类,其主要功能如下:

       1). Server 类主要负责与 Client 端的连接和数据通讯;

       2). Application 类主要负责 Function 的组织和 Response 的分发;

       3). Function 类主要负责 Function 的定义及调用模式;

       4). Response 类主要负责 Client 端指定 Function 请求的处理,如参数读取、返

           回值编辑及提交;

       5). Applcation 针对 Client 的请求连接可以通过 OnConnect 和 OnDisconnect回

           调函数得到连接 ID,也可以通过 RCSAppGetEvent 函数得到;

       6). 若 Function 的调用模式不是查询模式,则 Client 的请求应答由Application

           的处理线程调用执行,否则必须调用 RCSAppGetEvent 函数得到 Client 的请

           求应答ID,然后通过应答ID取参数和设置返回值等等操作,这时处理应答由外

           部程序自己处理;

       7). Fields 类主要负责字段集值和字段定义集的组包和拆包。

    d. 在 RC 3.0 及以前版本中,由于 Client 端只能调用 Server 端函数,所以 RPC 只

       能实现单向调用,这在大部分应用中已经足够,仍有以下几个方面的不足:

       1). RPC 物理通讯层和通讯协议层未分离,两者以绑定方式来提供调用接口,通讯

           层固定为 TCP 协议,物理通讯途径单一;

       2). RPC 通讯协议层中连接握手和登录应用合二为一,一次连接只能登录一个应用,

           无法切换登录多个应用。

       3). 若 Server 端需要调用 Client 端函数,则只能通过发送数据方式发送请求包,

           Client 收到请求包后把函数执行结果通过发送数据方式、或者调用 Server 端

           提供的函数方式返回给 Server 端。虽然也能通过另外途径来实现调用 Client

           端函数,但毕竟不方便且增加了开发难度。

    e. 从 RC 4.0 开始到当前 RC 5.0 版本的 RPC 框架解决了以前版本的不足,主要改进

       如下:

       1). 把 RPC 物理通讯层单独剥离出去,剩下的 RPC 框架定义为 RCK (remote call

           kernel 缩写)为远程调用内核,RCK 负责远程调用过程中的 RPC 通讯协议层、

           RPC 连接会话层、RPC 应用层;

       2). RPC 物理通讯层与通讯协议层分离后,增加了物理通讯的适配层接口,定义为

           RCK 通讯槽接口(RCKSlot)负责数据接收和发送,此接口可以由第三方来实现实

           际的数据传输接口,目前已经支持 TCP 协议和 SHM 共享内存通讯协议;

       3). RPC 通讯协议层中连接握手与登录应用分离,连接可以切换登录多个应用;

       4). RPC 连接握手成功后,连接双方都可以登录对方应用,也就是说实现了 RPC 双

           向函数调用,降低了双向函数调用的开发难度。

     f. RC 5.0 由六大类组成:Application 类、Function 类、Connection 类、Command

        类、Response  类和 Fields  类,其主要功能如下:

        1). Application 类主要负责 Function 的组织和 Response 的分发执行;

        2). Function    类主要负责 Function 的定义及按各模式调用;

        3). Connection  类主要负责登录对端应用,Command 请求执行,Response 应答

                        管理,以及发送和接收数据等等;

        4). Command     类主要负责函数参数传入,以及返回值和返回字段值读取;

        5). Response    类主要负责对端指定 Function 请求的执行处理,如:参数读取、

                        返回值编辑及提交;

        6). Fields      类主要负责字段集值和字段定义集的组包和拆包。

    上面介绍了跨操作系统及跨开发语言 RPC 通用框架 RC 组成的大类,下面我们来分析

通用 RPC 框架要点和难点。

    a. 如何建立连接会话?会话的建立离不开连接握手,而连接握手请求由谁发起?建立

       连接会话是 RPC 的前提,所以这个环节是重中之重。

    b. 建立了连接会话只表示连接双方的通讯链路合法,这时双方都可以给对方发送数据,

       也就是说已经建立起了 MQ (MQ 就是 Message Queue 缩写),可以当 MQ 使用了。

    c. 若要调用对方的函数则必须要登录到对方应用,要登录应用必须要知道对方的应用

       名和应用密码,否则如何保证应用的安全?在一个进程中应用名应该唯一,最好不

       区分应用名大小写。根据应用名和密码生成 MD5 编码,登录时可以使用 MD5 编码

       校验合法性。

    d. 登录应用成功后,就要读取应用的函数定义集。函数定义集的好处就是在远程调用

       函数之前,本地就已经检验函数参数是否合法,以及调用时直接参数值以二进制数

       据串序列化,而应用端直接就可以把二进制数据串反序列化为参数值,这样就降低

       了 RPC 固有开销,提高了 RPC 性能。

    e. 应用如何分配并执行函数调用请求?如何提高应用端的并发性能?这些都是高性能

       RPC 框架必须考量的指标,也是高可用 RPC 的核心技术和难点。

    f. 函数调用结果是否需要确认,必须在函数定义时就设置好。因为有些函数涉及到资

       源分配,若调用后对端未确认收到则要自动释放已分配的资源。

    g. 函数调用是否需要加密,也必须在函数定义时就设置好。因为有些函数需要参数和

       返回值都加密,而且加密必须要使用动态密码,相同参数在连续调用时保证通讯过

       程中数据各不相同。

    h. 函数调用过程中参数与返回值的序列化和反序列化,也是 RPC 的要点和难点。

5. RPC 注意事项

    RPC 远程过程调用与本地的函数调用有很多相似性,都是通过传入参数调用后返回结果,

但两者还是有很大差别,主要差别如下:

    a. 本地调用函数时,函数一定会被执行,而远程调用函数时,函数不一定会被执行,

       即便执行也不一定能够把结果返回给调用者。产生这种情况的原因很多,如:网络

       断开、调用超时、或者服务端应用关闭等等。

    b. 本地调用函数时,允许传递数据指针参数而不必拷贝数据,而远程调用函数时,不

       允许传递数据指针参数,而必须拷贝并传递数据到服务端。这是因为数据指针为进

       程空间中的虚拟内存地址,此地址在服务端中毫无意义。

    c. 本地调用和远程调用的性能效率差距很大,这取决于 RPC 固有开销所占的比重,以

       及服务端负载情况和调度分配开销。RPC 固有开销相对于本地调用高出几个数量级,

       本地调用的固有开销(压栈出栈操作)是纳秒级,而远程调用的固有开销是在毫秒级。

6. RC 使用示例 - RC 应用的函数定义

// 函数名
const KYString FN_GetDateTime    = "GetDateTime";
const KYString FN_GetInfo        = "GetInfo";

const KYString FN_GetFileAttr    = "GetFileAttr";
const KYString FN_SetFileAttr    = "SetFileAttr";

const KYString FN_FileExisted    = "FileExisted";
const KYString FN_DeleteFile     = "DeleteFile";
const KYString FN_MoveFile       = "MoveFile";

// GetDateTime 函数的定义
static void L_DoDefOfGetDateTime(TRCAppObj& AppObj)
{
TRCFuncObj* objFunc = AppObj.NewFuncObj(FN_GetDateTime, kdtNone, 0,
kfcmCdecl, (void*)&L_DoGetDateTime);
if (objFunc != NULL)
{
// 设置函数参数/返回值加密传输
objFunc->SetNeedEncrypt(true);

// ----------------- 返回字段定义 -----------------
objFunc->AddField("Value",       kdtDateTime,   0);
}
}

// GetInfo 函数的定义
static void L_DoDefOfGetInfo(TRCAppObj& AppObj)
{
TRCFuncObj* objFunc = AppObj.NewFuncObj(FN_GetInfo, kdtNone, 0,
kfcmStdcall, (void*)&L_DoGetInfo);
if (objFunc != NULL)
{
// 设置函数参数/返回值加密传输
objFunc->SetNeedEncrypt(true);

// ----------------- 返回字段定义 -----------------
objFunc->AddField("StartTime",   kdtDateTime,   0);
objFunc->AddField("Version",     kdtString,   255);
}
}

// GetFileAttr 函数的定义
static void L_DoDefOfGetFileAttr(TRCAppObj& AppObj)
{
TRCFuncObj* objFunc = AppObj.NewFuncObj(FN_GetFileAttr, kdtInteger, 0,
kfcmCdecl, (void*)&L_DoGetFileAttr);
if (objFunc != NULL)
{
// 设置函数参数/返回值加密传输
//objFunc->SetNeedEncrypt(true);

// ------------------- 参数定义 -------------------
objFunc->AddParam("FileName",    kdtString,  MAX_PATH);

// ----------------- 返回字段定义 -----------------
objFunc->AddField("Attrib",      kdtInteger,    0);
}
}

// SetFileAttr 函数的定义
static void L_DoDefOfSetFileAttr(TRCAppObj& AppObj)
{
TRCFuncObj* objFunc = AppObj.NewFuncObj(FN_SetFileAttr, kdtInteger, 0,
kfcmStdcall, (void*)&L_DoSetFileAttr);
if (objFunc != NULL)
{
// 设置函数参数/返回值加密传输
//objFunc->SetNeedEncrypt(true);

// ------------------- 参数定义 -------------------
objFunc->AddParam("FileName",    kdtString,  MAX_PATH);
objFunc->AddParam("Attrib",      kdtInteger,    0);
}
}

// FileExisted 函数的定义
static void L_DoDefOfFileExisted(TRCAppObj& AppObj)
{
TRCFuncObj* objFunc = AppObj.NewFuncObj(FN_FileExisted, kdtInteger, 0,
kfcmStdcall, (void*)&L_DoFileExisted);
if (objFunc != NULL)
{
// 设置函数参数/返回值加密传输
//objFunc->SetNeedEncrypt(true);

// ------------------- 参数定义 -------------------
objFunc->AddParam("FileName",    kdtString,  MAX_PATH);
}
}

// DeleteFile 函数的定义
static void L_DoDefOfDeleteFile(TRCAppObj& AppObj)
{
TRCFuncObj* objFunc = AppObj.NewFuncObj(FN_DeleteFile, kdtInteger, 0,
kfcmStdcall, (void*)&L_DoDeleteFile);
if (objFunc != NULL)
{
// 设置函数参数/返回值加密传输
//objFunc->SetNeedEncrypt(true);

// ------------------- 参数定义 -------------------
objFunc->AddParam("FileName",    kdtString,  MAX_PATH);
}
}

// MoveFile 函数的定义
static void L_DoDefOfMoveFile(TRCAppObj& AppObj)
{
TRCFuncObj* objFunc = AppObj.NewFuncObj(FN_MoveFile, kdtInteger, 0,
kfcmCdecl, (void*)&L_DoMoveFile);
if (objFunc != NULL)
{
// 设置函数参数/返回值加密传输
//objFunc->SetNeedEncrypt(true);

// ------------------- 参数定义 -------------------
objFunc->AddParam("OldName",     kdtString,  MAX_PATH);
objFunc->AddParam("NewName",     kdtString,  MAX_PATH);
}
}

// 执行应用的函数定义
void TDemoApp::DoFuncDefs()
{
// 检查参数
if (FAppObj == NULL)
return;

// 开始定义
FAppObj->Close();
FAppObj->BeginDefs();
FAppObj->ClearFuncObjs();

// 函数定义
L_DoDefOfGetDateTime(*FAppObj);
L_DoDefOfGetInfo(*FAppObj);
L_DoDefOfGetFileAttr(*FAppObj);
L_DoDefOfSetFileAttr(*FAppObj);
L_DoDefOfFileExisted(*FAppObj);
L_DoDefOfDeleteFile(*FAppObj);
L_DoDefOfMoveFile(*FAppObj);
L_DoDefOfDirExisted(*FAppObj);
L_DoDefOfCreateDir(*FAppObj);
L_DoDefOfRemoveDir(*FAppObj);
// ... ...

// 结束定义
FAppObj->EndDefs();
}


7. RC 使用示例 - RC 应用的实际函数
// GetDateTime
static void __cdecl L_DoGetDateTime()
{
RCKRespFieldByDate(0, Now());
}

// GetInfo
static void __stdcall L_DoGetInfo()
{
// 赋值
TRCResp::FieldByDate(0L, _StartTime);
TRCResp::FieldByStr(1L,  _VerInfo);
/*
RCKRespFieldByDate(0,  _Time);
RCKRespFieldByStr(1,  _Info, _Info.Length());
*/
}

// GetFileAttr
static long __cdecl L_DoGetFileAttr(const char* AFileName)
{
// 初始化
long result = krUnknown;

// 操作
try
{
long intAttrib = FileGetAttr(AFileName);
if (intAttrib == -1)
result = krFailure;
else
{
RCKRespFieldByInt(0, intAttrib);
result = krSuccess;
}
}
catch (...) {}

// 返回结果
return result;
}

// SetFileAttr
static long __stdcall L_DoSetFileAttr(const char* AFileName, long Attrib)
{
// 初始化
long result = krUnknown;

// 操作
try
{
if (FileSetAttr(AFileName, Attrib) == 0)
result = krSuccess;
else if (FileExists(AFileName))
result = krFailure;
else
result = krNotExist;
}
catch (...) {}

// 返回结果
return result;
}

// FileExisted
static long __stdcall L_DoFileExisted(const char* AFileName)
{
// 初始化
long result = krUnknown;

// 操作
try
{
result = Bool_Ret[FileExists(AFileName)];
}
catch (...) {}

// 返回结果
return result;
}

// DeleteFile
static long __stdcall L_DoDeleteFile(const char* AFileName)
{
// 初始化
long result = krUnknown;

// 操作
try
{
if (DeleteFile(AFileName))
result = krSuccess;
else if (FileExists(AFileName))
result = krFailure;
else
result = krNotExist;
}
catch (...) {}

// 返回结果
return result;
}

// MoveFile
static long __cdecl L_DoMoveFile(const char* AOldName, const char* ANewName)
{
// 初始化
long result = krUnknown;

// 操作
try
{
result = Bool_Ret[RenameFile(AOldName, ANewName)];
}
catch (...) {}

// 返回结果
return result;
}


8. RC 使用示例 - RC 的远程调用函数
// GetDateTime
static long L_GetDateTime(TRCConnObj* AConnObj, TDateTime& ADateTime)
{
// 初始化
long      result;
TRCCmdObj objCmd;

// 调用函数
objCmd.SetConnObj(AConnObj);
result = objCmd.Begin(FN_GetDateTime);
if (result == krSuccess)
{
result = objCmd.Execute(5000);
if (result == krSuccess)
ADateTime = objCmd.FieldAsDate(0L);

// 结束
objCmd.End();
}

// 返回结果
return result;
}

// GetInfo
static long L_GetInfo(TRCConnObj* AConnObj, TDateTime& AStartTime, KYString& AVerInfo)
{
// 初始化
long      result;
TRCCmdObj objCmd;

// 调用函数
objCmd.SetConnObj(AConnObj);
result = objCmd.Begin(FN_GetInfo);
if (result == krSuccess)
{
// 执行
result = objCmd.Execute(5000);
if (result == krSuccess)
{
AStartTime = objCmd.FieldAsDate(0L);
AVerInfo   = objCmd.FieldAsStr(1L);
}

// 结束
objCmd.End();
}

// 返回结果
return result;
}

// GetFileAttr
static long L_GetFileAttr(TRCConnObj* AConnObj, const KYString& AFileName, long& Attrib)
{
// 初始化
long      result;
TRCCmdObj objCmd;

// 调用函数
objCmd.SetConnObj(AConnObj);
result = objCmd.Begin(FN_GetFileAttr);
if (result == krSuccess)
{
// 设置参数值
objCmd.ParamByStr(0L, AFileName);

// 执行
result = objCmd.Execute(5000);
if (result == krSuccess)
{
result = objCmd.RetAsInt();
Attrib = objCmd.FieldAsInt(0L);
}

// 结束
objCmd.End();
}

// 返回结果
return result;
}

// SetFileAttr
static long L_SetFileAttr(TRCConnObj* AConnObj, const KYString& AFileName, long Attrib)
{
// 初始化
long      result;
TRCCmdObj objCmd;

// 调用函数
objCmd.SetConnObj(AConnObj);
result = objCmd.Begin(FN_SetFileAttr);
if (result == krSuccess)
{
// 执行
result = objCmd.ExecByParams(5000, (char*)AFileName, Attrib);
if (result == krSuccess)
result = objCmd.RetAsInt();

// 结束
objCmd.End();
}

// 返回结果
return result;
}

// FileExisted
static long L_FileExisted(TRCConnObj* AConnObj, const KYString& AFileName)
{
// 初始化
long      result;
TRCCmdObj objCmd;

// 调用函数
objCmd.SetConnObj(AConnObj);
result = objCmd.Begin(FN_FileExisted);
if (result == krSuccess)
{
// 执行
result = objCmd.ExecByParams(5000, (char*)AFileName);
if (result == krSuccess)
result = objCmd.RetAsInt();

// 结束
objCmd.End();
}

// 返回结果
return result;
}

// DeleteFile
static long L_DeleteFile(TRCConnObj* AConnObj, const KYString& AFileName)
{
// 初始化
long      result;
TRCCmdObj objCmd;

// 调用函数
objCmd.SetConnObj(AConnObj);
result = objCmd.Begin(FN_DeleteFile);
if (result == krSuccess)
{
// 执行
result = objCmd.ExecByParams(5000, (char*)AFileName);
if (result == krSuccess)
result = objCmd.RetAsInt();

// 结束
objCmd.End();
}

// 返回结果
return result;
}

// MoveFile
static long L_MoveFile(TRCConnObj* AConnObj, const KYString& AOldName,
const KYString& ANewName)
{
// 初始化
long      result;
TRCCmdObj objCmd;

// 调用函数
objCmd.SetConnObj(AConnObj);
result = objCmd.Begin(FN_MoveFile);
if (result == krSuccess)
{
// 执行
result = objCmd.ExecByParams(5000, (char*)AOldName, (char*)ANewName);
if (result == krSuccess)
result = objCmd.RetAsInt();

// 结束
objCmd.End();
}

// 返回结果
return result;
}


================================================================================
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  RPC TCP RC RCK