RPC 拡張開発ガイド

ユーザーファイル記述子コールバック

ユーザーファイル記述子コールバックでは、ユーザーが、コールバックにファイル記述子を登録して 1 つまたは複数のイベントタイプを指定できるようになります。これにより、RPC サーバーを使用して、Sun RPC ライブラリ用に書かれていないファイル記述子を扱えるようになります。

ユーザーファイル記述子コールバックを実装するために、2 つの新しい関数 svc_add_input(3NSL) と svc_remove_input(3NSL) が Sun RPC ライブラリに追加されました。これらの関数は、ファイル記述子とともに、コールバックの宣言または削除を行います。

この新しいコールバック機能を使用する際は、ユーザーは次を行う必要があります。

  1. 次の構文でユーザーコードを記述して、callback() 関数を作成します:

    typedef void (*svc_callback_t) (svc_input_id_t id, int fd, \
    unsigned int revents, void* cookie);

    callback() 関数に渡される 4 つのパラメータは、次のとおりです:

    • id - 各コールバックに識別子を提供する。この識別子は、コールバックを削除するのに使用できます。

    • fd - ユーザーのコールバックが待機する対象のファイル記述子。

    • revents - 発生したイベントを表す、符号無しの整数。このイベントセットは、コールバックが登録された時に指定されたリストのサブセットです。

    • cookie - コールバックが登録された時に指定された cookie。この cookie は、サーバーがコールバック時に必要とする特定のデータを指定することができます。

  2. サーバーが識別する必要のある、ファイル記述子、および読み取りや書き込みなどの関連イベントを登録するために、svc_add_input() を呼び出します。

    svc_input_id_t svc_add_input (int fd, unsigned int revents, \
    svc_callback_t callback, void* cookie);
    指定可能なイベントのリストについては、poll(2) を参照してください。

  3. ファイル記述子を指定します。このファイル記述子は、ソケットやファイルなどのエンティティにすることができます。

指定されたイベントのいずれかが発生すると、標準のサーバーループが svc_run() を介してユーザーコードを呼び出し、ユーザーのコールバックがファイル記述子 (ソケットまたはファイル) 上で必要な操作を実行します。

特定のコールバックが必要でなくなった場合は、そのコールバックを削除するために、対応する識別子を指定して svc_remove_input() を呼び出します。


例 1–4 RPC サーバーにユーザーファイル記述子を登録する方法

この例は、RPC サーバーにユーザーファイル記述子を登録する方法、およびユーザーが定義したコールバックを提供する方法を示します。この例では、サーバーとクライアント両方での日時を監視できます。

  1. この例の Makefile を次に示します。

       RPCGEN = rpcgen
    
    CLIENT = todClient
    CLIENT_SRC = todClient.c timeofday_clnt.c 
    CLIENT_OBJ = $(CLIENT_SRC:.c=.o) 
    
    SERVER = todServer
    SERVER_SRC = todServer.c timeofday_svc.c 
    SERVER_OBJ = $(SERVER_SRC:.c=.o)
    
    RPCGEN_FILES = timeofday_clnt.c timeofday_svc.c  timeofday.h
    
    CFLAGS += -I.
    
    RPCGEN_FLAGS = -N -C
    LIBS = -lsocket -lnsl
    
    all: ./$(CLIENT) ./$(SERVER)
    
    
    $(CLIENT): timeofday.h $(CLIENT_OBJ)
    			  cc -o $(CLIENT) $(LIBS) $(CLIENT_OBJ)  
    
    $(SERVER): timeofday.h $(SERVER_OBJ) 
    			  cc -o $(SERVER)  $(LIBS) $(SERVER_OBJ)  
    
    timeofday_clnt.c: timeofday.x
    		  	  $(RPCGEN) -l $(RPCGEN_FLAGS) timeofday.x> timeofday_clnt.c
    
    timeofday_svc.c: timeofday.x
    			  $(RPCGEN) -m $(RPCGEN_FLAGS) timeofday.x> timeofday_svc.c
    
    timeofday.h: timeofday.x
    			  $(RPCGEN) -h $(RPCGEN_FLAGS) timeofday.x> timeofday.h
    
    
    clean:
    			  rm -f $(CLIENT_OBJ) $(SERVER_OBJ) $(RPCGEN_FILES)
  2. timeofday.x ファイルは、この例の中でサーバーによって提供される RPC サービスを定義します。この例のサービスは、gettimeofday() および settimeofday() です。

    program TIMEOFDAY { 
    			  version VERS1 {
    					 	  int SENDTIMEOFDAY(string tod) = 1;
    
    					 	  string GETTIMEOFDAY() = 2;
    			  } = 1;
    } = 0x20000090;
  3. userfdServer.h ファイルは、この例におけるソケットで送られるメッセージの構造を定義します。

       #include "timeofday.h"
    #define PORT_NUMBER 1971
    
    /* 
     * 接続用のデータを保存するのに使用される構造
    	 *	(user fds test)
     */
    typedef struct {
    	/* 
     * このリンクのコールバック登録の ID
     */
        svc_input_id_t in_id;
        svc_input_id_t out_id;
        
        /* 
    		  * この接続から読み取られるデータ
    		  */
        char in_data[128];
        
        /* 
    		  * この接続に書き込まれるデータ
    		  */
        char out_data[128];
        char* out_ptr;
        
    } Link;
    
        void
    socket_read_callback(svc_input_id_t id, int fd, unsigned int events,
    								  void* cookie);
        void
    socket_write_callback(svc_input_id_t id, int fd, unsigned int events,
                          void* cookie);
        void
    socket_new_connection(svc_input_id_t id, int fd, unsigned int events,
                          void* cookie);
    
        void
    timeofday_1(struct svc_req *rqstp, register SVCXPRT *transp);
  4. todClient.c ファイルは、クライアントで日時がどのように設定されるかを示します。このファイルでは、RPC はソケットとともにでも、ソケットなしでも使用されます。

       #include "timeofday.h"
    
    #include <stdio.h>
    #include <netdb.h>
    #define PORT_NUMBER 1971
    
        void
    runClient();
        void
    runSocketClient();
    
    
    char* serv_addr;
    
        void
    usage()
    {
        puts("Usage: todClient [-socket] <server_addr>");
        exit(2);
    }
    
        int
    main(int argc, char* argv[])
    {
        CLIENT* clnt;
        int sockClient;
        
        if ((argc != 2) && (argc != 3))
            usage();
    
        sockClient = (strcmp(argv[1], "-socket") == 0);
    
    		/*
    		 * ソケットの使用を選択する (sockClient)。
    		 * ソケットが利用できない場合は、
    		 * ソケットなしで RPC を使用する (runClient)。
    		 */
        if (sockClient && (argc != 3))
            usage();
        
        serv_addr = argv[sockClient? 2:1];
    
        if (sockClient) {
            runSocketClient();
        } else {
            runClient();
        }
    
        return 0;
    }
    /*
    	 * ソケットなしで RPC を使用する
     */
        void
    runClient()
    {
        CLIENT* clnt;
        char* pts;
        char** serverTime;
        
        time_t now;
    
        clnt = clnt_create(serv_addr, TIMEOFDAY, VERS1, "tcp");
        if (NULL == clnt) {
            clnt_pcreateerror("Cannot connect to log server");
            exit(1);
        }
    
        time(&now);
        pts = ctime(&now);
    
        printf("Send local time to server\n");
        
        /* 
    		  * 現地の時刻を設定し、この時刻をサーバーへ送信する。
    		  */
        sendtimeofday_1(pts, clnt);
    
        /* 
    		  * サーバーに現在の時刻を尋ねる。
    		  */
        serverTime = gettimeofday_1(clnt);
    
        printf("Time received from server: %s\n", *serverTime);
        
        clnt_destroy(clnt);
    }
    
    /*
     * ソケットとともに RPC を使用する
     */
        void
    runSocketClient()
    /*
     * ソケットを作成する
     */
    {
        int s = socket(PF_INET, SOCK_STREAM, 0);
        struct sockaddr_in sin;
        char* pts;
        char buffer[80];
        int len;
        
        time_t now;
        struct hostent* hent;
        unsigned long serverAddr;
    
        if (-1 == s) {
            perror("cannot allocate socket.");
            return;
        }
        
        hent = gethostbyname(serv_addr);
        if (NULL == hent) {
            if ((int)(serverAddr = inet_addr(serv_addr)) == -1) {
                puts("Bad server address");
                return;
            }
        } else {
            memcpy(&serverAddr, hent->h_addr_list[0], sizeof(serverAddr));
        }
    
        sin.sin_port = htons(PORT_NUMBER);
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = serverAddr;
    
    		 /*
    		  * ソケットを接続する
    		  */
        if (-1 == connect(s, (struct sockaddr*)(&sin),
                          sizeof(struct sockaddr_in))) {
            perror("cannot connect the socket.");
            return;
        }
    
        time(&now);
        pts = ctime(&now);
    
    		/*
    		 * ソケット上にメッセージを記述する。
    		 * メッセージはクライアントの現在時刻。
    		 */
        puts("Send the local time to the server.");
        if (-1 == write(s, pts, strlen(pts)+1)) {
            perror("Cannot write the socket");
            return;
        }
    
    		 /*
    		  * ソケット上のメッセージを読み取る。
    		  * メッセージはサーバーの現在時刻。
    		  */
        puts("Get the local time from the server.");
        len = read(s, buffer, sizeof(buffer));
    
        if (len == -1) {
            perror("Cannot read the socket");
            return;
        }
        puts(buffer);
        
        puts("Close the socket.");
        close(s);
    }
  5. todServer.c ファイルは、サーバーサイドからの timeofday サービスの使用法を示します。

       #include "timeofday.h"
    #include "userfdServer.h"
    #include <stdio.h>
    #include <errno.h>
    #define PORT_NUMBER 1971
    
    int listenSocket;
    
    /* 
     * RPC サーバーの実装
     */
    
        int*
    sendtimeofday_1_svc(char* time, struct svc_req* req)
    {
        static int result = 0;
        
        printf("Server: Receive local time from client  %s\n", time);
        return &result;
    }
        
        char **
    gettimeofday_1_svc(struct svc_req* req)
    {
        static char buff[80];
        char* pts;
        time_t now;
        static char* result = &(buff[0]);
    
        time(&now);
        strcpy(result, ctime(&now));
    
        return &result;
    }
    
    /* 
    	 * ソケットサーバーの実装
     */
    
        int
    create_connection_socket()
    {
        struct sockaddr_in sin;
        int size = sizeof(struct sockaddr_in);
        unsigned int port;
    
    		 /*
    		  * ソケットを作成する
    		  */
        listenSocket = socket(PF_INET, SOCK_STREAM, 0);
    
        if (-1 == listenSocket) {
            perror("cannot allocate socket.");
            return -1;
        }
    
        sin.sin_port = htons(PORT_NUMBER);
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = INADDR_ANY;
        
        if (bind(listenSocket, (struct sockaddr*)&sin, sizeof(sin)) == -1) {
            perror("cannot bind the socket.");
            close(listenSocket);
            return -1;
        }
    		 /*
    		  * サーバーは、クライアント接続が
    		  * 作成されるのを待機する
    		  */
        if (listen(listenSocket, 1)) {
            perror("cannot listen.");
            close(listenSocket);
            listenSocket = -1;        
            return -1;
        }
    
    		 /* 
    		  * svc_add_input は、待機しているソケット上に、
    		  * 読み取りコールバック socket_new_connection を登録する。
    		  * 新しい接続が保留状態の時に、
    		  * このコールバックが呼び出される。*/
        if (svc_add_input(listenSocket, POLLIN,
                          socket_new_connection, (void*) NULL) == -1) {
            puts("Cannot register callback");
            close(listenSocket);
            listenSocket = -1;        
            return -1;
        }
            
        return 0;
    }
    
    /*
     * socket_new_connection コールバック関数を定義する
     */
        void
    socket_new_connection(svc_input_id_t id, int fd,
                          unsigned int events, void* cookie)
    {
        Link* lnk;
        int connSocket;
    
    		 /* 
    		  * ソケット上で接続が保留状態にある時に、
    		  * サーバーが呼び出される。この接続を今受け付ける。
    		  * コールは非-ブロッキング。
    		  * このコールを扱うためにソケットを作成する。
    		  */
        connSocket = accept(listenSocket, NULL, NULL);
        if (-1 == connSocket) {
            perror("Server: Error: Cannot accept a connection.");
            return;
        }
    
        lnk = (Link*)malloc(sizeof(Link));
        lnk->in_data[0] = 0;
        
    		 /*
    		  * 新規のコールバック socket_read_callback が作成された。
    		  */
        lnk->in_id = svc_add_input(connSocket, POLLIN,
    			       socket_read_callback, (void*)lnk);
    }
    /*
     * 新規のコールバック socket_read_callback が定義される
     */
        void
    socket_read_callback(svc_input_id_t id, int fd, unsigned int events,
                         void* cookie)
    {
        char buffer[128];
        int len;
        Link* lnk = (Link*)cookie;
    
        /* 
    		  * メッセージを読み取る。この読み取りコールはブロックは行わない。
    		  */
        len = read(fd, buffer, sizeof(buffer));
    
        if (len> 0) {
        	  /* 
    				* データを取得した。このソケット接続に
    				* 関連付けられたバッファー内にそのデータをコピーする。
    				*/
            strncat (lnk->in_data, buffer, len);
    
            /* 
    				* 完全なデータを受信したかどうかをテストする。
             * 完全なデータでない場合は、これは部分的な読み取りである。
    				*/
            if (buffer[len-1] == 0) {
                char* pts;
                time_t now;
                
                /* 
    					 * 受信した日時を出力する。
    					 */
                printf("Server: Got time of day from the client: \n %s",
                       lnk->in_data);
    
                /* 
    					 * 応答データをセットアップする 
    					 * (サーバーの現在の日時)。
    					 */
                time(&now);
                pts = ctime(&now);
    
                strcpy(lnk->out_data, pts);
                lnk->out_ptr = &(lnk->out_data[0]);
    
    					/* 
    					 * 応答の書き込み時にブロックを行わない
    					 * 書き込みコールバック (socket_write_callback) を登録する。
    					 * ソケットへの書き込みアクセス権を保持している場合は、
    					 * POLLOUT を使用することができる。
    					 */ 
                lnk->out_id = svc_add_input(fd, POLLOUT,
                                            socket_write_callback, (void*)lnk);
    
            }
        } else if (len == 0) {
        /* 
         * 相手側でソケットがクローズされた。ソケットをクローズする。
         */
    	close(fd);
        } else {
            /* 
    				* ソケットが相手側によりクローズされているか ?
    				*/
            if (errno != ECONNRESET) {
                /* 
    					 * クローズされていない場合は、これはエラーである。
    					 */
                perror("Server: error in reading the socket");
                printf("%d\n", errno);
            }
            close(fd);
        }
    }
    
    /*
     * socket_write_callback を定義する。
     * ソケットへの書き込みアクセス権を保持している場合は、
     * このコールバックが呼び出される。
     */
        void
    socket_write_callback(svc_input_id_t id, int fd, unsigned int events,
                          void* cookie)
    {
        Link* lnk = (Link*)cookie;
    
        /* 
    		  * 書き込む残りのデータ長を計算する。
    		  */
        int len = strlen(lnk->out_ptr)+1;
    
    /*
     * 時間をクライアントへ送信する
     */
        if (write(fd, lnk->out_ptr, len) == len) {
        /* 
    		  * すべてのデータが送られた。
    		  */
    
        	/* 
    		  	 * 2 つのコールバックの登録を解除する。
         	 * この登録解除は、ファイル記述子がクローズされる時に
             * この登録が自動的に削除されるため、
    			 * ここに例示されている。
    			 */
            svc_remove_input(lnk->in_id);
            svc_remove_input(lnk->out_id);
            
            /* 
    				* ソケットをクローズする。
    				*/
            close(fd);
        }
    }
    
        void
    main()
    {
        int res;
    
    		 /*
    		  * timeofday サービスおよびソケットを作成する
    		  */
        res = create_connection_socket();
        if (-1 == res) {
            puts("server: unable to create the connection socket.\n");
            exit(-1);
        }
        
        res = svc_create(timeofday_1, TIMEOFDAY, VERS1, "tcp");
        if (-1 == res) {
            puts("server: unable to create RPC service.\n");
            exit(-1);
        }
        
    		 /*
    		  ユーザーファイル記述子をポーリングする。
    		  */
        svc_run();
    }