利用tirpc庫實現簡單的客戶端和服務端

lsgxeva發表於2024-09-11

利用tirpc庫實現簡單的客戶端和服務端

來源 https://www.hlovefp.cn/blog/post/125.html

演示系統:openEuler release 22.03 LTS

1. 準備環境

Bash
yum install rpcgen -y                    # rpcgen命令
yum -y install libtirpc libtirpc-devel   # rpc庫
一個RPC程式通常包括(每個RPC過程由程式設計師、版本號和過程號唯一標誌):
    程式號
    版本號
    過程號 
    網路選擇         傳輸協議,主要是udp或tcp
    rpcbind服務      rpcbind是用來連線網路服務和透過網路地址的基礎服務,一般使用埠號111。
    外部資料表示XDR  在RPC客戶機方和伺服器方之間傳送的資料按XDR傳輸語法編碼。

2. 生成XDR檔案

RPC使用XDR標準傳送資料,參考:https://www.hlovefp.cn/blog/post/124.html

Bash
[root@localhost rpc]# cat request.x         # request.x 定義的客戶端伺服器資料結構
struct calculator {
    int   op;
    float arg1;
    float arg2;
    float result;
};

[root@localhost rpc]# rpcgen request.x       # 生成 request.h 和 request_xdr.c
C
[root@localhost rpc]# cat request.h
/*
 * Please do not edit this file.
 * It was generated using rpcgen.
 */

#ifndef _REQUEST_H_RPCGEN
#define _REQUEST_H_RPCGEN

#include <rpc/rpc.h>


#ifdef __cplusplus
extern "C" {
#endif


struct calculator {
    int op;
    float arg1;
    float arg2;
    float result;
};
typedef struct calculator calculator;

/* the xdr functions */

#if defined(__STDC__) || defined(__cplusplus)
extern  bool_t xdr_calculator (XDR *, calculator*);

#else /* K&R C */
extern bool_t xdr_calculator ();

#endif /* K&R C */

#ifdef __cplusplus
}
#endif

#endif /* !_REQUEST_H_RPCGEN */
Bash
[root@localhost rpc]# cat request_xdr.c 
/*
 * Please do not edit this file.
 * It was generated using rpcgen.
 */

#include "request.h"

bool_t
xdr_calculator (XDR *xdrs, calculator *objp)
{
    register int32_t *buf;

     if (!xdr_int (xdrs, &objp->op))
         return FALSE;
     if (!xdr_float (xdrs, &objp->arg1))
         return FALSE;
     if (!xdr_float (xdrs, &objp->arg2))
         return FALSE;
     if (!xdr_float (xdrs, &objp->result))
         return FALSE;
    return TRUE;
}

3. 服務端程式碼

C
#include <stdio.h>
#include <string.h>
#include <rpc/auth.h>
#include <rpc/svc.h>
#include "request.h"

#define CALCULATOR_PROG ((unsigned long)0x20000001)
#define CALCULATOR_VER  ((unsigned long)0x1)
#define CALCULATOR_PROC ((unsigned long)0x1)

#define CALCULATOR_ADD 0
#define CALCULATOR_SUB 1
#define CALCULATOR_MUL 2
#define CALCULATOR_DIV 3

void handle_calculator(struct svc_req *req, SVCXPRT *xprt)
{
    calculator c;
    
    memset(&c, 0x00, sizeof(c));
    
    // 獲取資料
    if (!svc_getargs(xprt, (xdrproc_t)xdr_calculator, (caddr_t)&c)) {
        printf("error: svc_getargs error.\n");
        svcerr_decode(xprt);
        return;
    }
    
    switch(c.op) {
        case CALCULATOR_ADD:
            c.result = c.arg1 + c.arg2;
            break;
        case CALCULATOR_SUB:
            c.result = c.arg1 - c.arg2;
            break;
        case CALCULATOR_MUL:
            c.result = c.arg1 * c.arg2;
            break;
        case CALCULATOR_DIV:
            if (c.arg2 != 0) {
                c.result = c.arg1 / c.arg2;
            }
            break;
        default:
            break;
    }
    
    if(!svc_sendreply(xprt, (xdrproc_t)xdr_calculator, (caddr_t)&c)) {
        printf("error: svc_sendreply error.\n");
    }
    
    if(!svc_freeargs(xprt, (xdrproc_t)xdr_calculator, (caddr_t)&c)) {
        printf("error: svc_freeargs error.\n");
    }
}

void dispatch_handle(struct svc_req *req, SVCXPRT *xprt)
{
    printf("rq_prog : %d     service program number\n", req->rq_prog);
    printf("rq_vers : %d     service protocol version\n", req->rq_vers);
    printf("rq_proc : %d     the desired procedure\n", req->rq_proc);
    
    // 當伺服器得到客戶機請求的結構時,伺服器程式需要呼叫函式svc_sendreply向客戶機傳送報文。
    // bool_t svc_sendreply(SVCXPRT *xprt, xdrproc_t outproc, char *out);

    switch (req->rq_proc) {
        case CALCULATOR_PROC:
            handle_calculator(req, xprt);
            return;
        case NULLPROC:
            svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL);
            return;
        default:
            svcerr_noproc(xprt);
            return;
    }
}

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

    // 1. 呼叫 svcudp_create 或 svctcp_create 生成一個SVCXPRT結構的指標
    //    SVCXPRT *svcudp_create(int sock);
    //    SVCXPRT *svctcp_create(int sock, unsigned int send_buf_size, unsigned int recv_buf_size);
    
    SVCXPRT *xprt_udp = svcudp_create(RPC_ANYSOCK);
    if (xprt_udp == NULL) {
        printf("error: svcudp_create error.\n");
        return -1;
    }
    
    SVCXPRT *xprt_tcp = svctcp_create(RPC_ANYSOCK, 0, 0);
    if (xprt_tcp == NULL) {
        printf("error: svctcp_create error.\n");
        return -1;
    }
    
    /*
    // 2. 呼叫 svc_register 註冊提供的服務
    //    bool_t svc_register(SVCXPRT *xprt, 
                           unsigned long prognum,
                           unsigned long versnum,
                           void (*dispatch)(svc_req *, SVCXPRT *),
                           unsigned long protocol);
                           */
    if(!svc_register(xprt_udp, CALCULATOR_PROG, CALCULATOR_VER, dispatch_handle, IPPROTO_UDP)) {
        printf("error: svc_register dispatch_calculator udp error.\n");
        return -1;
    }
    if(!svc_register(xprt_tcp, CALCULATOR_PROG, CALCULATOR_VER, dispatch_handle, IPPROTO_TCP)) {
        printf("error: svc_register dispatch_calculator tcp error.\n");
        return -1;
    }
    
    // 3. 呼叫svc_run
    //    void svc_run(void);
    svc_run();
    
    // 4. 程式退出時呼叫 svc_destroy 釋放
    //    svc_destroy(SVCXPRT *xprt)
    svc_destroy(xprt_udp);
    svc_destroy(xprt_tcp);

    return 0;
}

// gcc -I/usr/include/tirpc -ltirpc server.c request_xdr.c -o server

4. 客戶端程式碼

C
#include <stdio.h>
#include <string.h>
#include <rpc/clnt.h>
#include "request.h"

#define CALCULATOR_PROG ((unsigned long)0x20000001)
#define CALCULATOR_VER  ((unsigned long)0x1)
#define CALCULATOR_PROC ((unsigned long)0x1)

#define CALCULATOR_ADD 0
#define CALCULATOR_SUB 1
#define CALCULATOR_MUL 2
#define CALCULATOR_DIV 3

int main(char *argv[], int argc)
{
    const char *ip = "localhost";
    struct timeval timeout;
    calculator req, rsp;
    enum clnt_stat stat;
    char op = '0';
    

    
    // 1. 呼叫 clnt_create 生成一個CLIENT結構的指標
    // CLIENT *clnt_create(const char *ip, const rpcprog_t, const rpcvers_t, const char *);
    CLIENT * client = clnt_create(ip, CALCULATOR_PROG, CALCULATOR_VER, "tcp");
  //CLIENT * client = clnt_create(ip, CALCULATOR_PROG, CALCULATOR_VER, "udp");
    if (NULL == client) {
        printf("error: clnt_create error.\n");
        return -1;
    }

    // 2. 呼叫 clnt_call 來呼叫遠端過程
    // enum clnt_stat clnt_call(CLIENT *rh, rpcproc_t proc, xdrproc_t xargs, void *argsp, drproc_t xres, void *resp, struct timeval timeout)
    timeout.tv_sec  = 30;
    timeout.tv_usec = 0;
    
    while(1) {
        req.op = -1;
        printf("choose the operation(0-ADD 1-SUB 2-MUL 3-DIV 4-QUIT): ");
        scanf("%d", &(req.op));
        if (req.op > 3 || req.op < 0) break;

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

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

        //stat = rpc_call(ip, CALCULATOR_PROG, CALCULATOR_VER, CALCULATOR_PROC, (xdrproc_t)xdr_calculator, (char *)&req, (xdrproc_t)xdr_calculator, (char *)&rsp, "tcp");

        
        stat = clnt_call(client, CALCULATOR_PROC, (xdrproc_t)xdr_calculator, (char *)&req, (xdrproc_t)xdr_calculator, (char *)&rsp, timeout);
        if(stat != RPC_SUCCESS) {
            clnt_perror(client, "Call Failed");
            return -1;
        }
        
        printf("The Result is  %lf \n\n", rsp.result);
    }
    printf("\n");


    // 3. 呼叫 clnt_destroy 關閉連線
    // clnt_destroy(CLIENT *rh)
    clnt_destroy(client);

    return 0;
}

// gcc -I/usr/include/tirpc -ltirpc client.c request_xdr.c -o client

5. 執行

啟動服務端程式

Bash
[root@localhost rpc]#service rpcbind start

[root@localhost rpc]#./server

新視窗執行客戶端程式

Bash
[root@localhost rpc]# ./client
choose the operation(0-ADD 1-SUB 2-MUL 3-DIV 4-QUIT): 3
Input the first  number: 500
Input the second number: 20
The Result is  25.000000 

choose the operation(0-ADD 1-SUB 2-MUL 3-DIV 4-QUIT): 2
Input the first  number: 3
Input the second number: 8
The Result is  24.000000 

choose the operation(0-ADD 1-SUB 2-MUL 3-DIV 4-QUIT): 1
Input the first  number: 90
Input the second number: 1000
The Result is  -910.000000 

choose the operation(0-ADD 1-SUB 2-MUL 3-DIV 4-QUIT): 1
Input the first  number: 9
Input the second number: 100
The Result is  -91.000000 

choose the operation(0-ADD 1-SUB 2-MUL 3-DIV 4-QUIT): 0
Input the first  number: 9
Input the second number: 100
The Result is  109.000000 

choose the operation(0-ADD 1-SUB 2-MUL 3-DIV 4-QUIT): q

檢視埠

Bash
[root@localhost rpc]# netstat -anp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:965             0.0.0.0:*               LISTEN      21989/./server
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      852/rpcbind
tcp        0      0 127.0.0.1:965           127.0.0.1:855           ESTABLISHED 21989/./server
tcp        0      0 127.0.0.1:855           127.0.0.1:965           ESTABLISHED 22302/./client
tcp6       0      0 :::111                  :::*                    LISTEN      852/rpcbind 
udp        0      0 0.0.0.0:53297           0.0.0.0:*                           852/rpcbind 
udp        0      0 0.0.0.0:111             0.0.0.0:*                           852/rpcbind
udp        0      0 0.0.0.0:965             0.0.0.0:*                           21989/./server
udp6       0      0 :::111                  :::*                                852/rpcbind
udp6       0      0 :::52318                :::*                                852/rpcbind

=========== End

相關文章