ONC+ 開発ガイド

ファイル記述子の例

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