ONC+ Developer's Guide

User File Descriptor Callbacks

User file descriptor callbacks enable you to register file descriptors with callbacks, specifying one or more event types. Now you can use an RPC server to handle file descriptors that were not written for the Sun RPC library.

With previous versions of the Sun RPC library, you could use a server to receive both RPC calls and non-RPC file descriptors only if you wrote your own server loop, or used a separate thread to contact the socket API.

For user file descriptor callbacks, two new functions have been added to the Sun RPC library, svc_add_input(3NSL) and svc_remove_input(3NSL), to implement user file descriptor callbacks. These functions declare or remove a callback on a file descriptor.

When using this new callback feature you must:

When one of the specified events occurs, the standard server loop calls the user code through svc_run() and your callback performs the required operation on the file descriptor, socket or file.

When you no longer need a particular callback, call svc_remove_input() with the corresponding identifier to remove the callback.

Example of User File Descriptors

This example shows you how to register a user file descriptor on an RPC server and how to provide user defined callbacks. With this example you can monitor the time of day on both the server and the client.

The makefile for this example is shown below.

   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)

The timeofday.x file defines the RPC services offered by the server in this example. The services in this examples are gettimeofday() and settimeofday().

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

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

The userfdServer.h file defines the structure of messages sent on the sockets in this example.

   #include "timeofday.h"
#define PORT_NUMBER 1971

/* 
 * Structure used to store data for a connection. 
	 *	(user fds test). 
 */
typedef struct {
	/* 
 * Ids of the callback registration for this link. 
 */
    svc_input_id_t in_id;
    svc_input_id_t out_id;
    
    /* 
		  * Data read from this connection. 
		  */
    char in_data[128];
    
    /* 
		  * Data to be written on this connection. 
		  */
    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);

The todClient.c file shows how the time of day is set on the client. In this file, RPC is used with and without sockets.

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

		/*
		 * Choose to use sockets (sockClient). 
		 * If sockets are not available,
		 * use RPC without sockets (runClient).
		 */
    if (sockClient && (argc != 3))
        usage();
    
    serv_addr = argv[sockClient? 2:1];

    if (sockClient) {
        runSocketClient();
    } else {
        runClient();
    }

    return 0;
}
/*
	 * Use RPC without sockets
 */
    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");
    
    /* 
		  * Set the local time and send this time to the server. 
		  */
    sendtimeofday_1(pts, clnt);

    /* 
		  * Ask the server for the current time. 
		  */
    serverTime = gettimeofday_1(clnt);

    printf("Time received from server: %s\n", *serverTime);
    
    clnt_destroy(clnt);
}

/*
 * Use RPC with sockets
 */
    void
runSocketClient()
/*
 * Create a socket
 */
{
    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;

		 /*
		  * Connect the socket
		  */
    if (-1 == connect(s, (struct sockaddr*)(&sin),
                      sizeof(struct sockaddr_in))) {
        perror("cannot connect the socket.");
        return;
    }

    time(&now);
    pts = ctime(&now);

		/*
		 * Write a message on the socket. 
		 * The message is the current time of the client.
		 */
    puts("Send the local time to the server.");
    if (-1 == write(s, pts, strlen(pts)+1)) {
        perror("Cannot write the socket");
        return;
    }

		 /*
		  * Read the message on the socket. 
		  * The message is the current time of the server
		  */
    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);
}

The todServer.c file shows the use of the timeofday service from the server side.

   #include "timeofday.h"
#include "userfdServer.h"
#include <stdio.h>
#include <errno.h>
#define PORT_NUMBER 1971

int listenSocket;

/* 
 * Implementation of the RPC server. 
 */

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

/* 
	 * Implementation of the socket server. 
 */

    int
create_connection_socket()
{
    struct sockaddr_in sin;
    int size = sizeof(struct sockaddr_in);
    unsigned int port;

		 /*
		  * Create a socket
		  */
    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;
    }
		 /*
		  * The server waits for the client 
		  * connection to be created
		  */
    if (listen(listenSocket, 1)) {
        perror("cannot listen.");
        close(listenSocket);
        listenSocket = -1;        
        return -1;
    }

		 /* 
		  * svc_add_input registers a read callback, 
		  * socket_new_connection, on the listening socket. 
		  * This callback is invoked when a new connection 
		  * is pending. */
    if (svc_add_input(listenSocket, POLLIN,
                      socket_new_connection, (void*) NULL) == -1) {
        puts("Cannot register callback");
        close(listenSocket);
        listenSocket = -1;        
        return -1;
    }
        
    return 0;
}

/*
 * Define the socket_new_connection callback function
 */
    void
socket_new_connection(svc_input_id_t id, int fd,
                      unsigned int events, void* cookie)
{
    Link* lnk;
    int connSocket;

		 /* 
		  * The server is called when a connection is 
		  * pending on the socket. Accept this connection now. 
		  * The call is non-blocking. 
		  * Create a socket to treat the call.
		  */
    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;
    
		 /*
		  * New callback created, socket_read_callback.
		  */
    lnk->in_id = svc_add_input(connSocket, POLLIN,
			       socket_read_callback, (void*)lnk);
}
/*
 * New callback, socket_read_callback, is defined
 */
    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;

    /* 
		  * Read the message. This read call does not block. 
		  */
    len = read(fd, buffer, sizeof(buffer));

    if (len > 0) {
    	  /* 
				* Got some data. Copy it in the buffer 
				* associated with this socket connection. 
				*/
        strncat (lnk->in_data, buffer, len);

        /* 
				* Test if we receive the complete data.
         * Otherwise, this is only a partial read. 
				*/
        if (buffer[len-1] == 0) {
            char* pts;
            time_t now;
            
            /* 
					 * Print the time of day you received. 
					 */
            printf("Server: Got time of day from the client: \n %s",
                   lnk->in_data);

            /* 
					 * Setup the reply data  
					 * (server current time of day). 
					 */
            time(&now);
            pts = ctime(&now);

            strcpy(lnk->out_data, pts);
            lnk->out_ptr = &(lnk->out_data[0]);

					/* 
					 * Register a write callback (socket_write_callback)
					 * that does not block when writing a reply. 
					 * You can use POLLOUT when you have write 
					 * access to the socket
					 */ 
            lnk->out_id = svc_add_input(fd, POLLOUT,
                                        socket_write_callback, (void*)lnk);

        }
    } else if (len == 0) {
    /* 
     * Socket closed in peer.  Closing the socket. 
     */
	close(fd);
    } else {
        /* 
				* Has the socket been closed by peer? 
				*/
        if (errno != ECONNRESET) {
            /* 
					 * If no, this is an error. 
					 */
            perror("Server: error in reading the socket");
            printf("%d\n", errno);
        }
        close(fd);
    }
}

/*
 * Define the socket_write_callback. 
 * This callback is called when you have write 
 * access to the socket. 
 */
    void
socket_write_callback(svc_input_id_t id, int fd, unsigned int events,
                      void* cookie)
{
    Link* lnk = (Link*)cookie;

    /* 
		  * Compute the length of remaining data to write. 
		  */
    int len = strlen(lnk->out_ptr)+1;

/*
 * Send the time to the client
 */
    if (write(fd, lnk->out_ptr, len) == len) {
    /* 
		  * All data sent. 
		  */

    	/* 
		  	 * Unregister the two callbacks. This unregistration
     	 * is demonstrated here as the registration is 
		  	 * removed automatically when the file descriptor 
			 * is closed.
			 */
        svc_remove_input(lnk->in_id);
        svc_remove_input(lnk->out_id);
        
        /* 
				* Close the socket. 
				*/
        close(fd);
    }
}

    void
main()
{
    int res;

		 /*
		  * Create the timeofday service and a socket
		  */
    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);
    }
    
		 /*
		  Poll the user file descriptors.
		  */
    svc_run();
}