[实践OK]实现Linux环境下编程RPC通信之个人经验总结。

jackxiang 2010-12-26 13:47 | |
首先博主抛出一个疑问,如何实现windows和linux之间的rpc通信,这两个异构的系统如何才能建立变量传递通道,这相当困扰博主,如有好心人指点,将不胜感激。
好了,现在切入正题:今天博主在linux环境下实现了一个小小RPC通信,按照惯例,做一下总结吧。
原先博主用的是Red hat9,安装完red hat后不想系统不带GCC,然后博主跑遍各大linux论坛搜寻装GCC的步骤,虽然取得些微的进步,对linux也有一些初步的认识了,但是系统还是不能用。无奈,博主就跑去问老师如何解决linux安装C编译器,不想linux老师来了一句这个red hat他已经不玩好多年了,然后建议我去玩ubuntu。于是,博主就屁颠屁颠地跑去下载最新的ubuntu 10.4,然后安装。出乎意料的是,ubuntu安装要比red hat方便多了,基本属于傻瓜式操作。
所以,接下来博主要讲的基本都是基于ubuntu 10.4平台的。
虽然是新平台,但是ubuntu用的还是linux内核,所以基本和red hat差不多,只有些微差别。
这次操作比上次在red hat上操作顺利多了,博主是参照下文的步骤一步一步实现rpc的:
1.        根据rpc调用的功能,先不考虑rpc调用,编写一个平常的实现相应功能的程序。
如一个远程的文件传输的rpc调用,平常程序便是考虑文件存储在本地,直接打开读便可,如下:
#include <stdio.h>
#include <stdlib.h>
#define MAXNAME 20
#define MAXLENGTH 1024
char * readfile(char *);
int main()
{
    char name[MAXNAME];
    printf("Enter File Name: ");
    scanf("%s", name);
    printf("%s", readfile(name));
}
char * readfile(char * name)
{
    FILE *file = fopen(name, "r");
    char * buf = (char *)malloc(sizeof(char)*MAXLENGTH);
    if (file == NULL)
    {
        printf("File Cann't Be Open!");
        return 0;
    }
    printf("The File Content is : \n");
    while (fgets(buf, MAXLENGTH-1, file) != NULL)
    {
        return buf;
    }
    return NULL;
}
2.        把程序拆分为两部分,main函数和readfile函数,带有main的一部分是主动发起调用的,在rpc中相当于客户端,带有readfile函数的部分是提供相应的功能的,相当于服务器端。将代码拆分后要在客户端添加相应的rpc调用函数,clnt_create(RMACHINE, FILETRANSPROG, FILETRANSVERS,"tcp");
FILETRANSPROG便是trans.x中的程序名,FILETRANSVERS是版本名,使用tcp进行rpc调用。
拆分后代码如下:
客户端client.c:
#include <rpc/rpc.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include "trans.h"
#define WSVERS MAKEWORD(0, 2)
#define RMACHINE "localhost"
CLIENT *handle;
#define MAXNAME 20
#define MAXLENGTH 1024
char * readfile(char *);
int main()
{
    char name[MAXNAME];
    char * buf;
    printf("Enter File Name: ");
    scanf("%s", name);
   handle = clnt_create(RMACHINE, FILETRANSPROG, FILETRANSVERS,"tcp");
   if (handle == 0) {
         printf("Could Not Connect To Remote Server.\n");
         exit(1);
   }
    buf = readfile(name);
    printf("%s", buf);
    return 0;
}
服务器端server.c:
#include <rpc/rpc.h>
#include <stdio.h>
#include <stdlib.h>
#include "trans.h"
#define MAXNAME 20
#define MAXLENGTH 1024
char * readfile(char * name)
{
    FILE *file = fopen(name, "r");
    char * buf = (char *)malloc(sizeof(char)*MAXLENGTH);
    if (file == NULL)
    {
        printf("File Cann't Be Open!");
        return 0;
    }
    printf("The File Content is : \n");
    while (fgets(buf, MAXLENGTH-1, file) != NULL)
    {
        return buf;
    }
    return NULL;
}
3.        编写***.x文件。具体步骤可以参考Douglas的那本Internetworking With TCP/IP 的第三卷,客户端-服务器端编程与应用。
本程序的.x文件命名为trans.x内容如下:
const MAXLENGTH = 1024;
const MAXNAME = 20;
program FILETRANSPROG  //程序名
{
    version FILETRANSVERS  //版本名
    {
        string READFILE(string) = 1;  //调用的方法名
    } = 1;
} = 99;
4.        使用rpcgen编辑.x文件,在linux下输入命令
rpcgen   trans.x
若格式正确,编译无错误则产生三个文件trans.h,trans_svc.c(服务器端),trans_clnt.c(客户端)。因为上述trans.x中无自定义数据结构,所以没有xdr文件产生。
trans.h代码:
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#ifndef _TRANS_H_RPCGEN
#define _TRANS_H_RPCGEN
#include <rpc/rpc.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MAXLENGTH 1024
#define MAXNAME 20
#define FILETRANSPROG 99
#define FILETRANSVERS 1
#if defined(__STDC__) || defined(__cplusplus)
#define READFILE 1
extern  char ** readfile_1(char **, CLIENT *);
extern  char ** readfile_1_svc(char **, struct svc_req *);
extern int filetransprog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t);
#else /* K&R C */
#define READFILE 1
extern  char ** readfile_1();
extern  char ** readfile_1_svc();
extern int filetransprog_1_freeresult ();
#endif /* K&R C */
#ifdef __cplusplus
}
#endif
#endif /* !_TRANS_H_RPCGEN */
trans_svc.c代码:
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include "trans.h"
#include <stdio.h>
#include <stdlib.h>
#include <rpc/pmap_clnt.h>
#include <string.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifndef SIG_PF
#define SIG_PF void(*)(int)
#endif
static void
filetransprog_1(struct svc_req *rqstp, register SVCXPRT *transp)
{
   union {
            char *readfile_1_arg;
   } argument;
   char *result;
   xdrproc_t _xdr_argument, _xdr_result;
   char *(*local)(char *, struct svc_req *);
   switch (rqstp->rq_proc) {
   case NULLPROC:
            (void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);
            return;
   case READFILE:
            _xdr_argument = (xdrproc_t) xdr_wrapstring;
            _xdr_result = (xdrproc_t) xdr_wrapstring;
            local = (char *(*)(char *, struct svc_req *)) readfile_1;
            break;
   default:
            svcerr_noproc (transp);
            return;
   }
   memset ((char *)&argument, 0, sizeof (argument));
   if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
            svcerr_decode (transp);
            return;
   }
   result = (*local)((char *)&argument, rqstp);
   if (result != NULL && !svc_sendreply(transp, (xdrproc_t) _xdr_result, result)) {
            svcerr_systemerr (transp);
   }
   if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
            fprintf (stderr, "%s", "unable to free arguments");
            exit (1);
   }
   return;
}
int
main (int argc, char **argv)
{
   register SVCXPRT *transp;
   pmap_unset (FILETRANSPROG, FILETRANSVERS);
   transp = svcudp_create(RPC_ANYSOCK);
   if (transp == NULL) {
            fprintf (stderr, "%s", "cannot create udp service.");
            exit(1);
   }
   if (!svc_register(transp, FILETRANSPROG, FILETRANSVERS, filetransprog_1, IPPROTO_UDP)) {
            fprintf (stderr, "%s", "unable to register (FILETRANSPROG, FILETRANSVERS, udp).");
            exit(1);
   }
   transp = svctcp_create(RPC_ANYSOCK, 0, 0);
   if (transp == NULL) {
            fprintf (stderr, "%s", "cannot create tcp service.");
            exit(1);
   }
   if (!svc_register(transp, FILETRANSPROG, FILETRANSVERS, filetransprog_1, IPPROTO_TCP)) {
            fprintf (stderr, "%s", "unable to register (FILETRANSPROG, FILETRANSVERS, tcp).");
            exit(1);
   }
   svc_run ();
   fprintf (stderr, "%s", "svc_run returned");
   exit (1);
   /* NOTREACHED */
}
trans_clnt.c代码:
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include <memory.h> /* for memset */
#include "trans.h"
/* Default timeout can be changed using clnt_control() */
static struct timeval TIMEOUT = { 25, 0 };
char **
readfile_1(char **argp, CLIENT *clnt)
{
   static char *clnt_res;
   memset((char *)&clnt_res, 0, sizeof(clnt_res));
   if (clnt_call (clnt, READFILE,
            (xdrproc_t) xdr_wrapstring, (caddr_t) argp,
            (xdrproc_t) xdr_wrapstring, (caddr_t) &clnt_res,
            TIMEOUT) != RPC_SUCCESS) {
            return (NULL);
   }
   return (&clnt_res);
}
5.        编写客户端和服务器端接口。此部分可以说是最麻烦的部分,稍不注意便会出错,同样可以参考Douglas的那本书,但要注意的是他的服务器接口例程代码中的每个函数的第二个参数应该是CLIENT *clnt, 而非struct svc_req * rqstp
为本程序编写的代码如下:
客户端接口文件trans_cif.c:
#include <rpc/rpc.h>
#include <stdio.h>
#include "trans.h"/* Client-side stub interface routines written by programmer */
extern CLIENT * handle;
static char **ret;
char * readfile(char * name)
{
   char ** arg;
   arg = &name;
   ret = readfile_1(arg, handle);
   return ret==NULL ? NULL : *ret;
}
服务器端接口文件trans_sif.c:
#include <rpc/rpc.h>
#include <stdio.h>
#include "trans.h"
char * readfile(char *);
static char * retcode;
char ** readfile_1(char ** w, CLIENT *clnt)
{
   retcode = readfile(*(char**)w);
   return &retcode;
}
6.        编译链接客户端和服务器端程序
不管是客户端还是服务器端,都要链接三个文件,
客户端:程序文件+*** _clnt.c+客户端接口文件。
服务器端:程序文件+*** _svc.c+服务器端接口文件
同时每一段的三个文件都是互相关联的,编译出现错误时,可以根据提示查看三个文件进行debug
命令如下:
gcc -Wall -o trans_client client.c trans_clnt.c trans_cif.c
gcc -Wall -o trans_server server.c trans_svc.c trans_sif.c
7.        启动服务器端和客户端,大功告成。要先运行服务器端程序,再运行客户端程序。命令如下:
./trans_server
./trans_client
client启动后,提示输入要传输的文件名,输入后,server将文件的第一行传回,大功告成!
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/symbol89/archive/2009/06/21/4285142.aspx
----------------------------------------------------------------------------------------------------------------------------------------------------
博主按照上面的指示一步一步做下来,还算顺利。但是在链接服务端文件时爆出一个错误,意思是trans_svc.c这个文件有个undefined reference to 'readfile_1_svc'错误,经本人亲测,是由于trans_svc.c文件第37行原本应该是local = (char*(*)(char *,struct svc_reg *))readfile_1;的,但是rpc编译器翻译成local = (char*(*)(char *,struct svc_reg *))readfile_1_svc ,所以把_svc去掉就好了。
这里不得不提的是ubuntu的vi编辑器相当不好用。即没有装vim,操作起来也没有red hat的vi编译器那样用的那么顺手。这里强烈推荐鸟哥的vi编辑器入门手册。把vi编译器介绍的很详细,附上地址:http://linux.vbird.org/linux_basic/0310vi.php。
vi编辑器模式图(转载自鸟哥的私房菜)
vi模式下指令汇总(转载自鸟哥的私房菜)
最后强烈推荐《鸟哥的私房菜》这本书,相当棒的讲解linux的一套书(有专门的网站)。分上下册,上册讲解linux的基本知识和指令,下册讲linux架站的知识。很实用。
-----------------------------------------------------------------------------------------------------------------------------------------------------
最后运行./trans_server时系统会爆出cannot connect to...的错误(具体的我也忘了),
Cannot register service: RPC: Unable to receive; errno = Connection refused
unable to register (FILETRANSPROG, FILETRANSVERS, udp)

这是由于linux默认把端口映射服务关闭的缘故。这时可以参照下面的解决方法:

centos 6(在CentOS 6.3当中,portmap服务由rpcbind负责) :
yum -y install nfs-utils rpcbind
我们可以看出NFS的安装在Centos 5下和Centos 6下还是有比较大的差异,做个记录方便以后安装。
$ sudo apt-get install nfs-kernel-server nfs-common portmap
$ sudo dpkg-reconfigure portmap
--------------------------------------------------------------------------------------------------------
如果启动还有问题,那有可能是rpcbind服务没有起得来:

[root@iZ25dcp92ckZ rpc]# service rpcbind start
Redirecting to /bin/systemctl start  rpcbind.service

CeontOS参考:http://blog.chinaunix.net/uid-20639775-id-3399961.html

在出现如下提示的时候,注意选定“不将portmap 绑定在loopback 地址”
之后,系统会有如下提示:
Current registered services:
------------------------------------------------
100003 2 udp 2049 nfs
100003 3 udp 2049 nfs
100003 4 udp 2049 nfs

100003 2 tcp 2049 nfs
100003 3 tcp 2049 nfs
100003 4 tcp 2049 nfs
之后通过查看 /etc/default/portmap, 确保
#OPTIONS="-i 127.0.0.1"
前面的#号 被添加了
重启portmap服务:
$ sudo /etc/init.d/portmap restart
这样就可以了,但是要用root权限执行。
到这里所有我所遇到的问题都解决了,但是博主仍有疑问,如何实现windows和linux之间的rpc通信,这两个异构的系统如何才能建立变量传递通道,这相当困扰博主,如有好心人指点,将不胜感激。
======Below Add Time:2016-01-22=========
启动:
[root@iZ25dcp92ckZ rpc]# ./trans_server
连接:
[root@iZ25dcp92ckZ rpc]# ./trans_client
Enter File Name: /etc/hosts
127.0.0.1   iZ25dcp92ckZ    Jack'sAliYunVPS    jackxiang  localhost

本文出自 “只争朝夕” 博客,请务必保留此出处http://xiaovfight.blog.51cto.com/1625426/398745

作者:jackxiang@向东博客 专注WEB应用 构架之美 --- 构架之美,在于尽态极妍 | 应用之美,在于药到病除
地址:http://jackxiang.com/post/3900/
版权所有。转载时必须以链接形式注明作者和原始出处及本声明!


最后编辑: jackxiang 编辑于2016-1-22 13:48
评论列表
发表评论

昵称

网址

电邮

打开HTML 打开UBB 打开表情 隐藏 记住我 [登入] [注册]