Skip Navigation Links | |
Exit Print View | |
ONC+ Developer's Guide Oracle Solaris 11.1 Information Library |
1. Introduction to ONC+ Technologies
4. Programmer's Interface to RPC
5. Advanced RPC Programming Techniques
6. Porting From TS-RPC to TI-RPC
7. Multithreaded RPC Programming
8. Extensions to the Oracle Solaris RPC Library
One-way call using a simple counter service
Using a simple counter with non-blocking I/O
clnt_call() Configured as Non-Blocking
Client Connection Closure Callback
Example of client connection closure callback
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 Oracle Solaris RPC library.
With previous versions of the Oracle Solaris 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 Oracle Solaris 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:
Create your callback() function by writing user code with the following syntax:
typedef void (*svc_callback_t) (svc_input_id_t id, int fd, \ unsigned int revents, void* cookie);
The four parameters passed to the callback() function are:
Provides an identifier for each callback. This identifier can be used to remove a callback.
The file descriptor that your callback is waiting for.
An unsigned integer representing the events that have occurred. This set of events is a subset of the list given when the callback is registered.
The cookie given when the callback is registered. This cookie can be a pointer to specific data the server needs during the callback.
Call svc_add_input() to register file descriptors and associated events, such as read or write, that the server must be aware of.
svc_input_id_t svc_add_input (int fd, unsigned int revents, \ svc_callback_t callback, void* cookie);
A list of the events that can be specified is given inpoll(2) .
Specify a file descriptor. This file descriptor can be an entity such as a socket or a file.
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.
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(); }