本文从RPC的基础知识出发,结合一个在单机上运行的字典程序,完成一个在Linux环境下运行的客户端服务器程序。
参考博客:
1http://blog.csdn.net/shark863/article/details/2107356
2http://zhoulifa.bokee.com/6128714.html
本文中涉及的所有程序都在实际机器上执行通过。
目录
1---单机上的.c源文件代码
2--单机源文件运行截图
3--rpc文件编辑过程
4--rpc文件编辑运行过程的总结
5--rpc客户机服务器运行截图
1单机上的.c源文件代码如下:
/* dict.c -- main, initw, nextin, insertw, deletew, lookupw */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#define MAXWORD 50 /* maximum length of a command or word */
#define DICTSIZ 100 /* maximum number of entries in dictionary. */
char dict[DICTSIZ][MAXWORD + 1]; /* storage for a dictionary of words */
int nwords = 0; /* number of words in the dictionary */
/* 函数原型 */
int nextin(char *cmd, char *word);
int initw(void);
int insertw(const char *word);
int deletew(const char *word);
int lookupw(const char *word);
/* ------------------------------------------------------------------
* main -- insert, delete, or lookup words in a dictionary as specified
* ------------------------------------------------------------------ */
int main(int argc, char *argv[])
{
char word[MAXWORD + 1]; /* space to hold word from input line */
char cmd;
int wordlen; /* length of input word */
printf("I----initialized\ni----insert\nd----delete\nl----lookup\nq----quits\n");
printf("Please input after ***\n");
printf("***");
while (1) {
wordlen = nextin(&cmd, word);
if (wordlen < 0) {
exit(0);
}
switch (cmd) {
case 'I': /* 初始化 */
initw();
printf("Dictionary initialized to empty.\n");
break;
case 'i': /* 插入 */
insertw(word);
printf("%s inserted.\n", word);
break;
case 'd': /* 删除 */
if (deletew(word)) {
printf("%s deleted.\n", word);
} else {
printf("%s not found.\n", word);
}
break;
case 'l': /* 查询 */
if (lookupw(word)) {
printf("%s was found.\n", word);
} else {
printf("%s was not found.\n", word);
}
break;
case 'q': /* 退出 */
printf("Program quits.\n");
exit(0);
break;
default: /* 非法输入 */
printf("command %c invalid.\n", cmd);
break;
} /* end of switch */
printf("***");
} /* end of while */
return 0;
} /* end of main */
/* ------------------------------------------------------------------
* nextin -- read a command and(possibly) a word from the next input line
* ------------------------------------------------------------------ */
int nextin(char *cmd, char *word)
{
int i,ch;
ch = getc(stdin);
while (isspace(ch)) {
ch = getc(stdin);
} /* end of while */
if (ch == EOF) {
return (-1);
}
*cmd = (char) ch;
if(*cmd=='I' || *cmd=='q'){
return 0;
}
if(*cmd!='i' && *cmd!='d' && *cmd!='l'){
return 0;
}
ch = getc(stdin);
while (isspace(ch)) {
ch = getc(stdin);
} /* end of while */
if (ch == EOF) {
return (-1);
}
if (ch == '\n') {
return (0);
}
i = 0;
while (!isspace(ch)) {
if (++i > MAXWORD) {
printf("error: word too long.\n");
exit(1);
}
*(word+i-1)= ch;
ch = getc(stdin);
} /* end of while */
*(word+i) = '\0'; /* 原来的代码这里有问题 */
return i;
} /* end of nextin */
/* ------------------------------------------------------------------
* initw -- initialize the dictionary to contain no words at all
* ------------------------------------------------------------------ */
int initw(void)
{
nwords = 0;
return 1;
} /* end of initw */
/* ------------------------------------------------------------------
* insertw -- insert a word in the dictionary
* ------------------------------------------------------------------ */
int insertw(const char *word)
{
strcpy(dict[nwords], word);
nwords++;
return (nwords);
} /* end of insertw */
/* ------------------------------------------------------------------
* deletew -- delete a word from the dictionary
* ------------------------------------------------------------------ */
int deletew(const char *word)
{
int i;
for (i = 0; i < nwords; i++) {
if (strcmp(word, dict[i]) == 0) {
nwords--;
strcpy(dict[i], dict[nwords]);
return (1);
}
} /* end of for */
return (0);
} /* end of deletew */
/* ------------------------------------------------------------------
* lookupw -- look up a word in the dictionary
* ------------------------------------------------------------------ */
int lookupw(const char *word)
{
int i;
for (i = 0; i < nwords; i++) {
if (strcmp(word, dict[i]) == 0) {
return (1);
}
}
return (0);
}
2单机运行截图
3RPC文件编辑过程
1)编写.x文件,并保存为*.x。本文中,为dict.x文件。
/* dict.x */
/* RPC declaration for dictionary program */
const MAXWORD = 50;
const DICTSIZ = 100;
program DICTPROG{
version DICTVERS{
int INITW( void ) = 1;
int INSERTW(string) = 2;
int DELETEW(string) = 3;
int LOOKUPW(string) =4;
}=1;
}=11;
2)使用命令#rpcgen dict.x产生三个文件。分别为dict.h、dict_clnt.c、dict_svc.c。
dict.h中保存了一些头文件的申明、dict.x文件中定义的产量等
dict_svc.c中包含了服务器端的main()函数。主要功能是针对不同的远程调用函数名,实现对服务器端执行的函数名,参数类型,返回值类型的封装,已实现针对不同的远程调用,使用统一的服务器端调用格式。
dict_clnt.c中对客户端可能调用的每一个函数的封装,将客户端对远程进程调用以统一的格式发送至服务器。封装的内容包括:客户端信息、调用函数名、调用函数参数类型、调用函数返回值类型。
3)使用#rpcgen -Ss dict_srv_func.c dict.x命令产生服务器端编辑源程序文件。
使用#rpcgen -Sc dict_client.c dict.x命令产生客户端编辑源程序文件。
对dict_srv_func.c 和 dict_client.c 文件进行修改,完成所需功能。
本实例的dict_srv_func.c源代码如下:
/*
* This is sample code generated by rpcgen.
* These are only templates and you can use them
* as a guideline for developing your own functions.
*/
#include "dict.h"
char dict[DICTSIZ][MAXWORD + 1]; /* storage for a dictionary of words */
int nwords = 0; /* number of words in the dictionary */
int initbool =0;
/* ------------------------------------------------------------------
* initw -- initialize the dictionary to contain no words at all
* ------------------------------------------------------------------ */
int initw(void)
{
if(initbool==1)
return 1;
nwords = 0;
initbool=1;
return 0;
} /* end of initw */
/* ------------------------------------------------------------------
* insertw -- insert a word in the dictionary
* ------------------------------------------------------------------ */
int insertw(const char *word)
{
// printf("insertw1:in server\n");
strcpy(dict[nwords], word);
// printf("insertw2:in server\n");
nwords++;
return (nwords);
} /* end of insertw */
/* ------------------------------------------------------------------
* deletew -- delete a word from the dictionary
* ------------------------------------------------------------------ */
int deletew(const char *word)
{
int i;
for (i = 0; i < nwords; i++) {
if (strcmp(word, dict[i]) == 0) {
nwords--;
strcpy(dict[i], dict[nwords]);
return (1);
}
} /* end of for */
return (0);
} /* end of deletew */
/* ------------------------------------------------------------------
* lookupw -- look up a word in the dictionary
* ------------------------------------------------------------------ */
int lookupw(const char *word)
{
int i;
for (i = 0; i < nwords; i++) {
if (strcmp(word, dict[i]) == 0) {
return (1);
}
}
return (0);
}
int *
initw_1_svc(void *argp, struct svc_req *rqstp)
{
static int result;
/*
* insert server code here
*/
result = initw();
return &result;
}
int *
insertw_1_svc(char **argp, struct svc_req *rqstp)
{
static int result;
/*
* insert server code here
*/
// printf("before:in insertw_1_svc\n");
result = insertw(*argp);
// printf("after:in insertw_1_svc\n");
return &result;
}
int *
deletew_1_svc(char **argp, struct svc_req *rqstp)
{
static int result;
/*
* insert server code here
*/
result = deletew(*argp);
return &result;
}
int *
lookupw_1_svc(char **argp, struct svc_req *rqstp)
{
static int result;
/*
* insert server code here
*/
result = lookupw(*argp);
return &result;
}
本实例dict_client.c源代码如下所示:
/*
* This is sample code generated by rpcgen.
* These are only templates and you can use them
* as a guideline for developing your own functions.
*/
#include "dict.h"
int nextin(char *cmd, char *word)
{
int i,ch;
ch = getc(stdin);
while (isspace(ch)) {
ch = getc(stdin);
} /* end of while */
if (ch == EOF) {
return (-1);
}
*cmd = (char) ch;
if(*cmd=='I' || *cmd=='q'){
return 0;
}
if(*cmd!='i' && *cmd!='d' && *cmd!='l'){
return 0;
}
ch = getc(stdin);
while (isspace(ch)) {
ch = getc(stdin);
} /* end of while */
if (ch == EOF) {
return (-1);
}
if (ch == '\n') {
return (0);
}
i = 0;
while (!isspace(ch)) {
if (++i > MAXWORD) {
printf("error: word too long.\n");
exit(1);
}
*(word+i-1)= ch;
ch = getc(stdin);
} /* end of while */
*(word+i) = '\0';
return i;
} /* end of nextin */
void
dictprog_1(char *host)
{
CLIENT *clnt;
int *result_1;
char *initw_1_arg;
int *result_2;
char * insertw_1_arg;
int *result_3;
char * deletew_1_arg;
int *result_4;
char * lookupw_1_arg;
#ifndef DEBUG
clnt = clnt_create (host, DICTPROG, DICTVERS, "udp");
if (clnt == NULL) {
clnt_pcreateerror (host);
exit (1);
}
#endif /* DEBUG */
char word[MAXWORD + 1]; /* space to hold word from input line */
char cmd;
int wordlen; /* length of input word */
printf("I----initialized\ni----insert\nd----delete\nl----lookup\nq----quits\n");
printf("Please input after ***\n");
printf("***");
while (1) {
wordlen = nextin(&cmd, word);
if (wordlen < 0) {
exit(0);
}
switch (cmd) {
case 'I': /* 初始化 */
result_1=initw_1((void *)initw_1_arg,clnt);
if(result_1==(int *)NULL)
clnt_perror(clnt,"call failed");
else if(*result_1==1)
printf("Dictionary have already initialized.\n");
else
printf("Dictionary initialized to empty.\n");
break;
case 'i': /* 插入 */
insertw_1_arg = word;
result_2=insertw_1(&insertw_1_arg,clnt);
if(result_2==(int *)NULL)
clnt_perror(clnt,"call failed");
else
printf("%s inserted.\n", word);
break;
case 'd': /* 删除 */
deletew_1_arg = word;
result_3=deletew_1(&deletew_1_arg,clnt);
if(result_3==(int *)NULL)
clnt_perror(clnt,"call failed");
else if(*result_3==1) {
printf("%s deleted.\n", word);
} else if(*result_3==0){
printf("%s not found.\n", word);
}
break;
case 'l': /* 查询 */
lookupw_1_arg = word;
result_4 = lookupw_1(&lookupw_1_arg,clnt);
if(result_4==(int *)NULL)
clnt_perror(clnt,"call failed");
else if(*result_4==1) {
printf("%s was found.\n", word);
} else if(*result_4==0){
printf("%s was not found.\n", word);
}
break;
case 'q': /* 退出 */
printf("Program quits.\n");
exit(0);
break;
default: /* 非法输入 */
printf("command %c invalid.\n", cmd);
break;
} /* end of switch */
printf("***");
} /* end of while */
#ifndef DEBUG
clnt_destroy (clnt);
#endif /* DEBUG */
}
int
main (int argc, char *argv[])
{
char *host;
if (argc < 2) {
printf ("usage: %s server_host\n", argv[0]);
exit (1);
}
host = argv[1];
dictprog_1 (host);
exit (0);
}
4)使用#rpcgen -Sm dict.x > makefile 命令产生makefile文件
对makefile文件做如下修改:将dict_srv_func.c加入到TARGETS_SVC.c中,将dict_client.c加入到TARGETS_CLNT.c中。
在clean末尾处加入“*~”,与前面内容使用空格隔开。
本实例中makefile文件内容如下所示:
# This is a template Makefile generated by rpcgen
# Parameters
CLIENT = dict_client
SERVER = dict_server
SOURCES_CLNT.c =
SOURCES_CLNT.h =
SOURCES_SVC.c =
SOURCES_SVC.h =
SOURCES.x = dict.x
TARGETS_SVC.c = dict_svc.c dict_srv_func.c
TARGETS_CLNT.c = dict_clnt.c dict_client.c
TARGETS = dict.h dict_clnt.c dict_svc.c
OBJECTS_CLNT = $(SOURCES_CLNT.c:%.c=%.o) $(TARGETS_CLNT.c:%.c=%.o)
OBJECTS_SVC = $(SOURCES_SVC.c:%.c=%.o) $(TARGETS_SVC.c:%.c=%.o)
# Compiler flags
CFLAGS += -g
LDLIBS += -lnsl
RPCGENFLAGS =
# Targets
all : $(CLIENT) $(SERVER)
$(TARGETS) : $(SOURCES.x)
rpcgen $(RPCGENFLAGS) $(SOURCES.x)
$(OBJECTS_CLNT) : $(SOURCES_CLNT.c) $(SOURCES_CLNT.h) $(TARGETS_CLNT.c)
$(OBJECTS_SVC) : $(SOURCES_SVC.c) $(SOURCES_SVC.h) $(TARGETS_SVC.c)
$(CLIENT) : $(OBJECTS_CLNT)
$(LINK.c) -o $(CLIENT) $(OBJECTS_CLNT) $(LDLIBS)
$(SERVER) : $(OBJECTS_SVC)
$(LINK.c) -o $(SERVER) $(OBJECTS_SVC) $(LDLIBS)
clean:
$(RM) core $(TARGETS) $(OBJECTS_CLNT) $(OBJECTS_SVC) $(CLIENT) $(SERVER) *~
5)使用make命令产生客户端可执行文件:dict_client,产生服务器端可执行文件:dict_server。
在服务器端执行命令(必须以root身份):./dict_server
在客户端执行命令:./dict_client 127.0.0.1
也可以有多个客户端,一个服务器端,字典的初始化只会再所以终端中执行一次。
6)清除产生的过程文件和最终的可执行文件,使用命令make clean。
4rpc编码过程中的总结
1)makefile的程序调试过程:1.make clean删除产生的可执行文件和中间文件;2.rpcgen dict.x 产生两个过程文件;3.对源代码进行修改;
4make,重新生成可执行文件;5产生修改结果
2)可直接使用string类型,在rpc自动生成过程中,会自动转化为char *类型。
3)rpc涉及的几个函数功能介绍:
svc_getargs:作用(在服务器端解码rpc远程调用过程中的参数);参数(rpc建立的连接、参数的类型、服务器端的对应存储空间)
svcerr_decode:作用(给出不能解码出远程调用过程中参数的文字提示)
4)对dict_client.c中进行的修改:
a)将对远程调用函数的参数赋值。若函数名为*,则将该函数的参数赋值给*_version_arg,否则将发生segmentation fault。
b)在客户端,对远程函数*的调用,变成对"*-version"的调用。
c)远程调用函数的参数只能有一个,在对远程函数进行调用的过程中的参数为:(&*_version_arg,clnt),即使用的是原来参数的地址和clnt。
d)远程调用的返回值是本地函数返回值的地址,即要对远程调用的返回值进行取值,才能得到想要的返回值。
e)对每个远程调用函数的返回值,都要进行一次NULL判断,(void *)NULL要进行指针转化,因为返回值本身是一个指针。若为空,
则clnt_perror(clnt,"call failed")。
5)对dict_srv_func.c中进行的修改:
a)将对应的变量及函数体写入文件中。
b)在*_svc(远程调用函数对应的服务器端函数)中添加语句:result = *(*argp)(其中*为函数名称)。
6)client端指向期待server端返回的地址空间,要在调用之前被预先分配,否知出现segmentation fault,server端要返回给client端的
内存空间要自行分配和释放。(来自博客,未来验证)
7)关于segmentation fault(core dumped)。可以在segmentation fault之后,使用gdb和生成的core文件,得到发送段错误的具体位置。
要生成core文件,必须对终端进行一定的设置,且对每个终端的设置是彼此独立的,只有对发生段错误的终端进行设置,才能在运行程序
的当前目录产生core文件。
具体设置如下
#ulimit -a //查看ulimit状态
#ulimit -c unlimited //设置为无限大
#ulimit unlimited //设置为无限大
gdb [exec file] [core file]
在gdb中使用where查看错误发生的地点
5rpc客户机服务器运行截图
1服务器端运行截图
2客户端1运行截图
3客户端3运行截图