ONC+ 開発ガイド

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

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

Sun RPC ライブラリの以前のバージョンでは、ユーザーが独自のサーバーループを記述するか、ソケット API にコンタクトをとる別個のスレッドを使用する場合にのみ、RPC コールおよび非 RPC ファイル記述子の両方をサーバーに受け入れさせることが可能でした。

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

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

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

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

ファイル記述子の例

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

このプログラムの 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)

timeofday.x ファイルは、この例の中でサーバーによって提供される RPC サービスを定義します。この例のサービスは、gettimeofday() および settimeofday() です。

program TIMEOFDAY { 
			  version VERS1 {
					 	  int SENDTIMEOFDAY(string tod) = 1;

					 	  string GETTIMEOFDAY() = 2;
			  } = 1;
} = 0x20000090;

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);

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);
}

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();
}