Linux下C實現RPC


RPC

在介紹RPC之前,我們有必要先介紹一下IPC

進程間通信(IPC,Inter-Process Communication),指至少兩個進程或線程間傳送數據或信號的一些技術或方法。進程是計算機系統分配資源的最小單位。每個進程都有自己的一部分獨立的系統資源,彼此是隔離的。為了能使不同的進程互相訪問資源並進行協調工作,才有了進程間通信。這些進程可以運行在同一計算機上或網絡連接的不同計算機上。 進程間通信技術包括消息傳遞、同步、共享內存和遠程過程調用。 IPC是一種標准的Unix通信機制。

有兩種類型的進程間通信(IPC):

  • 本地過程調用(LPC)LPC用在多任務操作系統中,使得同時運行的任務能互相會話。這些任務共享內存空間使任務同步和互相發送信息。
  • 遠程過程調用(RPC)RPC類似於LPC,只是在網上工作。RPC開始是出現在Sun微系統公司和HP公司的運行UNIX操作系統的計算機中。

為什么要用RPC呢?

我們知道,同一主機的同一程序之間因為有函數棧的存在,使得函數的相互調用很簡單。但是兩個程序(程序A和程序B)分別運行在不同的主機上,A想要調用B的函數來實現某一功能,那么使用常規的方法就是不可實現的,RPC就是干這個活的

RPC的核心並不在於使用什么協議。RPC的目的是讓你在本地調用遠程的方法,而對你來說這個調用是透明的,你並不知道這個調用的方法是部署哪里。通過RPC能解耦服務,這才是使用RPC的真正目的。RPC的原理主要用到了動態代理模式,至於http協議,只是傳輸協議而已。簡單的實現可以參考spring remoting,復雜的實現可以參考dubbo。

總結一下

  • RPC就是從一台機器(客戶端)上通過參數傳遞的方式調用另一台機器(服務器)上的一個函數或方法(可以統稱為服務)並得到返回的結果。
  • RPC 會隱藏底層的通訊細節(不需要直接處理Socket通訊或Http通訊) RPC 是一個請求響應模型。
  • 客戶端發起請求,服務器返回響應(類似於Http的工作方式) RPC 在使用形式上像調用本地函數(或方法)一樣去調用遠程的函數(或方法)。

XDR

XDR是一個數據描述和數據編碼的標准,XDR的主要作用就是在不同進程間傳遞消息參數時,避免因為計算機平台的不一致而導致數據傳送接收異常。它可以對消息參數按照一定的順序編碼,放在一個數據包里(通常是在內存中申請一個一定大小的字符串緩沖區),然后把這個數據包發送給其他平台,然后在按照之前編碼的順序依次解碼,並可以獲得原來的消息參數。

rpcgen

rpcgen介紹

可以看執行man rpcgen在man page中查看rpcgen的介紹,不得不說man page真的是很強大的工具,里面的介紹說明言簡意賅,還能練英語 😄

原文:
rpcgen is a tool that generates C code to implement an RPC protocol. The input to rpcgen is a language similar to C known as RPC Language (Remote Procedure Call Language).
rpcgen is normally used as in the first synopsis where it takes an input file and generates up to four output files. If the infile is named proto.x, then rpcgen will generate a header file in proto.h, XDR routines in proto_xdr.c, server-side stubs in proto_svc.c, and client-side stubs in proto_clnt.c. With the -T option, it will alsogenerate the RPC dispatch table in proto_tbl.i. With the -Sc option, it will also generate sample code which would illustrate how to use the remote procedures on the client side. This code would be created in proto_client.c. With the -Ss option, it will also generate a sample server code which would illustrate how to write the remote procedures. This code would be created in proto_server.c..

翻譯:
rpcgen是一種工具,它可以生成實現RPC的C語言代碼。使用rpcgen時,你需要提供一個與C語言類似的RPC語言源文件。
rpcgen通常通過一個源文件生成四個輸出文件。如果輸入文件是proto.x,rpcgen將生成一個頭文件proto.h,XDR規則proto_xdr.c,服務端存根proto_svc.c,客戶端存根proto_clt.c 若使用-T選項,還會生成一個proto_tbl.i,使用-Sc選項,將會生成一個客戶端的rpc樣例,使用-Ss選項將會生成一個服務端的rpc樣例。

rpcgen命令選項

usage: rpcgen infile
    rpcgen [-abkCLNTM][-Dname[=value]] [-i size] [-I [-K seconds]] [-Y path] infile
    rpcgen [-c | -h | -l | -m | -t | -Sc | -Ss | -Sm] [-o outfile] [infile]
    rpcgen [-s nettype]* [-o outfile] [infile]
    rpcgen [-n netid]* [-o outfile] [infile]
options:
-a      generate all files, including samples
-b      backward compatibility mode (generates code for SunOS 4.1)
-c      generate XDR routines
-C      ANSI C mode
-Dname[=value]  define a symbol (same as #define)
-h      generate header file
-i size     size at which to start generating inline code
-I      generate code for inetd support in server (for SunOS 4.1)
-K seconds  server exits after K seconds of inactivity
-l      generate client side stubs
-L      server errors will be printed to syslog
-m      generate server side stubs
-M      generate MT-safe code
-n netid    generate server code that supports named netid
-N      supports multiple arguments and call-by-value
-o outfile  name of the output file
-s nettype  generate server code that supports named nettype
-Sc     generate sample client code that uses remote procedures
-Ss     generate sample server code that defines remote procedures
-Sm         generate makefile template 
-t      generate RPC dispatch table
-T      generate code to support RPC dispatch tables
-Y path     directory name to find C preprocessor (cpp)
-5      SysVr4 compatibility mode
--help      give this help list
--version   print program version

部分options解釋:

-a 生成所有源程序,包括客戶機和服務器源程序。

-C 使用ANSI C標准生成編碼。

-c 生成xdr轉碼C程序。(file_xdr.c)。

-l 生成客戶機stubs。(file_clnt.c)

-m 生成服務器stubs,但是不生成main函數。(file_svc.c)

-s  rpcgen –C –s tcp file.x,生成服務器stubs,用tcp協議,同時生成了main函數。(file_svc.c)

-h 生成頭文件。

-Sc 生成骨架客戶機程序,(file_client.c),生成后還需要手動添加代碼。

-Ss 生成服務器程序,(file_server.c),生成后還需要手動添加代碼。

Linux上編寫rpc-demo

Linux下面的RPC模型是SUN RPC (ONC RPC),使用了XDR來編碼/解碼數據。gcc提供了一些標准數據類型的XDR filter(比如整型,浮點型,字符串等)。對於自定義數據類型,則需要自己編寫XDR filter來處理。

你可以使用rpcgen來幫你自動生成xdr filter,但是,該工具需要你提供一個 .x 文件。

這里提供兩個demo作為參考

  • 簡易計算器

(1)編寫.x文件

/* 
 * filename: calculator.x 
 * function: 定義遠程調用中常量、非標准數據類型以及調用過程
 */

const ADD = 0;
const SUB = 1;
const MUL = 2;
const DIV = 3;

struct CALCULATOR
{
    int op; /* 0-ADD, 1-SUB, 2-MUL, 3-DIV */
    float arg1;
    float arg2;
    float result;
};

program CALCULATOR_PROG
{
    version CALCULATOR_VER
    {
        struct CALCULATOR CALCULATOR_PROC(struct CALCULATOR) = 1;
    } = 1;  /*  版本號=1 */
} = 0x20000001; /* RPC程序編號 */

(2)使用rpcgen生成文件

想一步到位,生成所有文件的話執行:

$ rpcgen -a calculator.x

(如果你需要分步執行也是可以的,根據上面寫的rpcgen命令使用不同的參數)

生成以下文件:

文件名 作用
Makefile.calculator 該文件用於編譯所有客戶機,服務器代碼
calculator.h 聲明用到的變量和函數
calculator_xdr.c 非標准數據類型的編碼
calculator_clnt.c 將遠程調用代理( proxying ) 為本地OS調用,並將調用參數打包成一個消息,然后將此消息發送給服務器。由rpcgen生成,程序員一般無需修改
calculator_svc.c 將通過網絡輸入的請求轉換為本地過程調用,即負責解壓服務器上收到的消息,並調用實際的服務器端、應用程序級的實現,程序員一般不用修改
calculator_client.c 骨架客戶端程序,需要自行修改
calculator_server.c 骨架服務端程序,需要自行修改

(3)修改骨架程序

這里需要我們自己修改calculator_client.c和calculator_server.c來實現功能

注:/* -<<< Add to test*/ 包含的為添加的代碼

calculator_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 "calculator.h"


void
calculator_prog_1(char *host)
{
	CLIENT *clnt;
	struct CALCULATOR  *result_1;
	struct CALCULATOR  calculator_proc_1_arg;

#ifndef	DEBUG
	clnt = clnt_create (host, CALCULATOR_PROG, CALCULATOR_VER, "udp");
	if (clnt == NULL) {
		clnt_pcreateerror (host);
		exit (1);
	}
#endif	/* DEBUG */

	/* -<<< Add to test*/
    char c;

    printf("choose the operation:\n\t0---ADD\n\t1---SUB\n\t2---MUL\n\t3---DIV\n");
    c = getchar();

	if(c>'3' && c<'0'){
		printf("error:operate/n");
		exit(1);
	}
    calculator_proc_1_arg.op = c-'0';

    printf("input the first number:");
    scanf("%f", &calculator_proc_1_arg.arg1);

    printf("input the second number:");
    scanf("%f", &calculator_proc_1_arg.arg2);

    /* -<<< Add to test*/

	result_1 = calculator_proc_1(&calculator_proc_1_arg, clnt);
	if (result_1 == (struct CALCULATOR *) NULL) {
		clnt_perror (clnt, "call failed");
	}
#ifndef	DEBUG
	clnt_destroy (clnt);
#endif	 /* DEBUG */

    /* -<<< Add to test*/
    printf("The Result is %.3f \n", result_1->result);
    /* -<<< Add to test*/
}


int
main (int argc, char *argv[])
{
	char *host;

	if (argc < 2) {
		printf ("usage: %s server_host\n", argv[0]);
		exit (1);
	}
	host = argv[1];
	calculator_prog_1 (host);
exit (0);
}

calculator_server.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 "calculator.h"

struct CALCULATOR *
calculator_proc_1_svc(struct CALCULATOR *argp, struct svc_req *rqstp)
{
	static struct CALCULATOR  result;

	/*
	 * insert server code here
	 */
 	/* -<<< Add to test*/
    switch(argp->op){
		case ADD:
            result.result = argp->arg1 + argp->arg2;
            break;
        case SUB:
            result.result = argp->arg1 - argp->arg2;
            break;
        case MUL:
            result.result = argp->arg1 * argp->arg2;
            break;
        case DIV:
            result.result = argp->arg1 / argp->arg2;
            break;
        default:
            break;

    }
    /* -<<< Add to test*/
	return &result;
}

(4)編譯

rpcgen幫我們生成了makefile,使用make -f命令調用即可

$ make -f Makefile.calculator 

(5)執行

image-20200509175431941

  • 服務端時間獲取

(1)編寫.x文件

program DATE_PROG { 
    version DATE_VERS { 
        long GET_DATE(void) = 1; 
    } = 1; 
} = 0x20000002; 

(2)使用rpcgen生成文件

$ rpcgen -a date.x

(3)修改骨架程序

date_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 "date.h"


void
date_prog_1(char *host)
{
	CLIENT *clnt;
	long  *result_1;
	char *get_date_1_arg;

#ifndef	DEBUG
	clnt = clnt_create (host, DATE_PROG, DATE_VERS, "udp");
	if (clnt == NULL) {
		clnt_pcreateerror (host);
		exit (1);
	}
#endif	/* DEBUG */

	result_1 = get_date_1((void*)&get_date_1_arg, clnt);
	if (result_1 == (long *) NULL) {
		clnt_perror (clnt, "call failed");
	}

 	/* -<<< Add to test*/
	printf("%ld\n",*result_1);
 	/* -<<< Add to test*/
	 
#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];
	date_prog_1 (host);
exit (0);
}

date_server.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 "date.h"
#include <time.h>

long *
get_date_1_svc(void *argp, struct svc_req *rqstp)
{
	static long  result;

	/*
	 * insert server code here
	 */

 	/* -<<< Add to test*/
	result = (long)time(0);
 	/* -<<< Add to test*/
	return &result;
}

(4)編譯(同上一個例子)

(5)執行

image-20200514111446088

出現的問題

  • 服務器無法啟動,錯誤如下:Cannot register service: RPC: Unable to receive; errno = Connection refused
    unable to register (TESTPROG, VERSION, udp).

    問題原因:系統沒有安裝 portmap 或者沒有啟動 portmap 端口映射。

    解決方法: 在新版的 ubuntu 中 portmap 被 rpcbind 所代替, 所以需要啟動 rpcbind 服務。

    service rpcbind restart
    

參考資料

https://www.jianshu.com/p/b0343bfd216e
https://www.jianshu.com/p/b33fc01c1f59
http://blog.chinaunix.net/uid-20644632-id-2220585.html
https://blog.csdn.net/weixin_30538029/article/details/95014067


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM