对于rpc说明书文件test.x,其中定义服务器过程以及他们的参数和结果。
使用rpcgen必须要生成的几个文件:
rpcgen –C -M test.x //-C生成ANSI C的代码, -M
test.h:过程及其参数的说明
tes_xdr.c: 用于rpc外部数据表示
test_clnt.c :客户端存根
test_svc.c : 服务器存根
对于说明书文件test.x(至于说明书文件能放那些东西,用什么格式表示,这里不做介绍)
const NAMELEN = 256;
typedef string PATHNAME<NAMELEN>;
const MAXCHUNK = 256;
struct test_in
{
PATHNAME pathname;
uint32_t mode;
uint64_t mtime;
};
struct test_out
{
int res;
uint64_t filesize;
int isvalide;
uint64_t mtime;
chunkinfo chunks<MAXCHUNK>;
};
program CS //程序号
{
version CS_VERS //版本号
{
struct test_out cs_open(struct test_in) = 1; //过程号
} = 2;
} = 0x31230000;
说明书文件内容与C头文件的对应关系
test.h分析
#ifdef __cplusplus
extern "C" { //ANSI C
#endif
#define NAMELEN 256
typedef char *PATHNAME;
#define MAXCHUNK 256
//说明书中结构的定义
struct test_in {
PATHNAME pathname;
uint32_t mode;
uint64_t mtime;
};
typedef struct test_in test_in;
struct test_out {
int res;
uint64_t filesize;
int isvalide;
uint64_t mtime;
struct {
u_int chunks_len;
chunkinfo *chunks_val;
} chunks;
};
typedef struct test_out test_out;
// 对应说明书中的程序号和版本号
#define CS 0x31230000
#define CS_VERS 2
#if defined(__STDC__) || defined(__cplusplus)
#define cs_open 1 //方法号
//客户端,服务器过程的声明
extern enum clnt_stat cs_open_2(struct test_in *, struct test_out *, CLIENT *);
extern bool_t cs_open_2_svc(struct test_in *, struct test_out *, struct svc_req *);
extern int cs_2_freeresult (SVCXPRT *, xdrproc_t, caddr_t); //释放解码参数时分配的空间
……
test_xdr.c分析
bool_t
xdr_PATHNAME (XDR *xdrs, PATHNAME *objp) //将C的数据转换成XDR格式
{
register int32_t *buf;
// rpc提供一系列方法如xdr_int(), xdr_string()用来编码C语言的基本类型
if (!xdr_string (xdrs, objp, NAMELEN))
return FALSE;
return TRUE;
}
bool_t
xdr_test_in (XDR *xdrs, test_in *objp) //对于结构,将每个数据段进行逐一转换
{
register int32_t *buf;
if (!xdr_PATHNAME (xdrs, &objp->pathname))
return FALSE;
if (!xdr_uint32_t (xdrs, &objp->mode))
return FALSE;
if (!xdr_uint64_t (xdrs, &objp->mtime))
return FALSE;
return TRUE;
XDR的编码格式如下:(注意char,short都是四字节的数据)
XDR流对象的操作
void xdrmem_create(XDR *xdrs, char *addr, unsigned int size, enum xdr_op op);
根据选项(XDR_ENCODE, XDR_DECODE, XDR_FREE),往xdrs流对象中(写,读,释放)数据,addr为流的缓冲区首地址,size为缓冲区长度(流中所有的数据不能超过这个长度)。
test_clnt.c分析
/* Default timeout can be changed using clnt_control() */
static struct timeval TIMEOUT = { 25, 0 }; //超时重连时间
enum clnt_stat
cs_open_2(struct test_in *argp, struct test_out *clnt_res, CLIENT *clnt)
{
// 使用clnt_call调用客户端函数,需先将数据转化为xdr格式(xdr_test_in, xdr_test_out)作为参数传到clnt_call,
return (clnt_call(clnt, cs_open,
(xdrproc_t) xdr_test_in, (caddr_t) argp,
(xdrproc_t) xdr_test_out, (caddr_t) clnt_res,
TIMEOUT));
}
/*
从客户端调用的实现上可以看出,输入参数,输出参数的空间必须提前分配好(包括结构中指针指向的空间 ,如test_out中chunks的数据空间必须在调用前分配好,否则rpc调用返回后往该地址上写数据会引发段错误
*/
wcw师兄blog(http://blog.chinaunix.net/u1/37472/showart_1006758.html)上分析rpc输入,输出参数的文章讲的比较好,我在dnfs上也测试过了,用于rpc服务器端解码结果分配的数据空间,不能在过程中显示free,如果free掉,这些空间无法被sendreply传送给客户端,释放的工作交由RPC服务器端需要显式定义的额外方法(对于加了-M参数的)中的xdr_free完成,故如果没有加上该过程,会造成内存泄露。
int cs_2_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
{
xdr_free(xdr_result, result);
return 1;
}
如果在服务器调用方法内就free为输出参数分配空间,则server会挂掉,提示内容为double free。
test_svc.c分析
……
static void
cs_2(struct svc_req *rqstp, register SVCXPRT *transp)
{
// 共用体存放输入参数及结构,根据不同的调用号,设置不同的值
union {
struct test_in cs_open_2_arg;
} argument;
/* 故在服务器端的输入输出参数的空间不用自己分配,但如果参数中包含指针,指针指向的空间需要实现分配好,rpc只在站上定义了参数结构,参数中只分配了一个指针变量的空间,如果不事先分配好空间,而往上面写数据,就会造成段错误 */
union {
struct test_out cs_open_2_res;
} result;
bool_t retval;
// 输入,输出参数转换方法
xdrproc_t _xdr_argument, _xdr_result;
// 调用的方法
bool_t (*local)(char *, void *, struct svc_req *);
// 根据调用方法号判断
switch (rqstp->rq_proc) {
case NULLPROC:
(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);
return;
// 根据不同的调用,设置xdr方法,过程调用方法
case cs_open:
_xdr_argument = (xdrproc_t) xdr_test_in;
_xdr_result = (xdrproc_t) xdr_test_out;
local = (bool_t (*) (char *, void *, struct svc_req *))cs_open_2_svc;
break;
default:
svcerr_noproc (transp);
return;
}
//解码输入的参数 svc_getargs
memset ((char *)&argument, 0, sizeof (argument));
if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
svcerr_decode (transp);
return;
}
// 调用相应的方法
retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp);
//发送相应结果给客户端
if (retval > 0 && !svc_sendreply(transp, (xdrproc_t) _xdr_result, (char *)&result)) {
svcerr_systemerr (transp);
}
// 释放使用svc_getargs解码xdr数据时分配的空间
if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
fprintf (stderr, "%s", "unable to free arguments");
exit (1);
}
if (!cs_2_freeresult (transp, _xdr_result, (caddr_t) &result))
fprintf (stderr, "%s", "unable to free results");
return;
}
int
main (int argc, char **argv) //服务器例程启动入口
{
register SVCXPRT *transp;
pmap_unset (CS, CS_VERS);
//建立一个基于udp/ip的服务传输点
transp = svcudp_create(RPC_ANYSOCK);
if (transp == NULL) {
fprintf (stderr, "%s", "cannot create udp service.");
exit(1);
}
//将程序号,版本号与服务传输点关联
if (!svc_register(transp, CS, CS_VERS, cs_2, IPPROTO_UDP)) {
fprintf (stderr, "%s", "unable to register (CS, CS_VERS, udp).");
exit(1);
}
//建立一个基于tcp/ip的服务传输点
transp = svctcp_create(RPC_ANYSOCK, 0, 0);
if (transp == NULL) {
fprintf (stderr, "%s", "cannot create tcp service.");
exit(1);
}
if (!svc_register(transp, CS, CS_VERS, cs_2, IPPROTO_TCP)) {
fprintf (stderr, "%s", "unable to register (CS, CS_VERS, tcp).");
exit(1);
}
svc_run (); //等待客户端请求
fprintf (stderr, "%s", "svc_run returned");
exit (1);
/* NOTREACHED */
}