ONC+ Developer's Guide

Client Connection Closure Callback

Client connection closure callback enables the server for connection-oriented transport to detect that the client has disconnected. The server can take the necessary action to recover from transport errors. Transport errors occur when a request arrives at the server, or when the server is waiting for a request and the connection is closed.

The connection closure callback is called when no requests are currently being executed on the connection. If the client connection is closed when a request is being executed, the server executes the request but a reply may not be sent to the client. The connection closure callback is called when all pending request are completed.

When a connection closure occurs, the transport layer sends an error message to the client. The handler is attached to a service using svc_control() for example as follows:

svc_control(service, SVCSET_RECVERRHANDLER, handler);

The arguments of svc_control() are:

  1. A service or an instance of this service. When this argument is a service, any new connection to the service inherits the error handler. When this argument is an instance of the service, only this connection gets the error handler.

  2. The error handler callback. The prototype of this callback function is:

    void handler(const SVCXPRT *svc, const boot_t IsAConnection);

For further information see the svc_control(3NSL) man page.


Note –

For XDR unmarshalling errors, if the server is unable to unmarshal a request, the message is destroyed and an error is returned directly to the client.


Example of client connection closure callback

This example implements a message log server. A client can use this server to open a log (actually a text file), to store message log, and then to close the log.

The log.x file describes the log program interface.

enum log_severity { LOG_EMERG=0, LOG_ALERT=1, LOG_CRIT=2, LOG_ERR=3,
		    LOG_WARNING=4, LOG_NOTICE=5, LOG_INFO=6 };

program LOG { 
			  version LOG_VERS1 {
						  int OPENLOG(string ident) = 1;

						  int CLOSELOG(int logID) = 2;

						  oneway WRITELOG(int logID, log_severity severity,
				string message) = 3;
			  } = 1;
} = 0x20001971;

The two procedures OPENLOG and CLOSELOG open and close a log that is specified by its logID. The WRITELOG() procedure, declared as oneway for the example, logs a message in an opened log. A log message contains a severity attribute, and a text message.

This is the makefile for the log server. Use this makefile to call the log.x file.

RPCGEN = rpcgen

CLIENT = logClient
CLIENT_SRC = logClient.c log_clnt.c log_xdr.c
CLIENT_OBJ = $(CLIENT_SRC:.c=.o) 

SERVER = logServer
SERVER_SRC = logServer.c log_svc.c log_xdr.c
SERVER_OBJ = $(SERVER_SRC:.c=.o)

RPCGEN_FILES = log_clnt.c log_svc.c log_xdr.c log.h

CFLAGS += -I.

RPCGEN_FLAGS	= -N -C
LIBS = -lsocket -lnsl

all: log.h ./$(CLIENT) ./$(SERVER)


$(CLIENT): log.h $(CLIENT_OBJ)
			  cc -o $(CLIENT) $(LIBS) $(CLIENT_OBJ)  

$(SERVER): log.h $(SERVER_OBJ) 
			  cc -o $(SERVER)  $(LIBS) $(SERVER_OBJ)  

$(RPCGEN_FILES): log.x
			  $(RPCGEN) $(RPCGEN_FLAGS) log.x

clean:
			  rm -f $(CLIENT_OBJ) $(SERVER_OBJ) $(RPCGEN_FILES)

logServer.c shows the implementation of the log server. As the log server opens a file to store the log messages, it registers a closure connection callback in openlog_1_svc(). This callback is used to close the file descriptor even if the client program forgets to call the closelog() procedure (or crashes before doing so). This example demonstrates the use of the connection closure callback feature to free up resources associated to a client in an RPC server.

#include "log.h"
#include <stdio.h>
#include <string.h>

#define NR_LOGS 3

typedef struct {
    SVCXPRT* handle;
    FILE* filp;
    char* ident;
} logreg_t;


static logreg_t logreg[NR_LOGS];
static char* severityname[] = {"Emergency", "Alert", "Critical", "Error",
                               "Warning", "Notice", "Information"};

    static void
close_handler(const SVCXPRT* handle, const bool_t);


    static int
get_slot(SVCXPRT* handle)
{
    int i;
    
    for (i = 0; i < NR_LOGS; ++i) {
        if (handle == logreg[i].handle) return i;
    }
    return -1;
}

    static FILE*
_openlog(char* logname)
/*
 * Open a log file
 */
{
    FILE* filp = fopen(logname, "a");
    time_t t;

    if (NULL == filp) return NULL;
    
    time(&t);
    fprintf(filp, "Log opened at %s\n", ctime(&t));

    return filp;
}

    static void
_closelog(FILE* filp)
{
    time_t t;

    time(&t);
    fprintf(filp, "Log close at %s\n", ctime(&t));
		/*
 	 * Close a log file
 	 */
    fclose(filp);
}
    
    int*
openlog_1_svc(char* ident, struct svc_req* req)
{
    int slot = get_slot(NULL);
    FILE* filp;
    static int res;
    time_t t;
    
    if (-1 != slot) {
        FILE* filp = _openlog(ident);
        if (NULL != filp) {
            logreg[slot].filp = filp;
            logreg[slot].handle = req->rq_xprt;
            logreg[slot].ident = strdup(ident);

		/*
 	 * When the client calls clnt_destroy, or when the 
 	 * client dies and clnt_destroy is called automatically, 
 	 * the server executes the close_handler callback
 	 */
            if (!svc_control(req->rq_xprt, SVCSET_RECVERRHANDLER,
			     							(void*)close_handler)) {
						 puts("Server: Cannot register a connection closure callback");
						 exit(1);
	    			}
        }
        
    }
    res = slot;
    return &res;
}

    int*
closelog_1_svc(int logid, struct svc_req* req)
{
    static int res;

    if ((logid >= NR_LOGS) || (logreg[logid].handle != req->rq_xprt)) {
        res = -1;
        return &res;
    }
    logreg[logid].handle = NULL;
    _closelog(logreg[logid].filp);
    res = 0;
    return &res;
}

/*
 * When there is a request to write a message to the log, 
 * write_log_1_svc is called
 */
    void*
writelog_1_svc(int logid, log_severity severity, char* message,
               struct svc_req* req)
{
    if ((logid >= NR_LOGS) || (logreg[logid].handle != req->rq_xprt)) {
        return NULL;
    }
		/*
		 * Write message to file
		 */
    fprintf(logreg[logid].filp, "%s (%s): %s\n",
            logreg[logid].ident, severityname[severity], message);
    return NULL;
}

    static void
close_handler(const SVCXPRT* handle, const bool_t dummy)
{
    int i;
    
		/* 
		 * When the client dies, the log is closed with closelog
		 */
    for (i = 0; i < NR_LOGS; ++i) {
        if (handle == logreg[i].handle) {
            logreg[i].handle = NULL;
            _closelog(logreg[i].filp);
        }
    }
}

The logClient.c file shows a client using the log server.

#include "log.h"
#include <stdio.h>

#define MSG_SIZE 128

    void
usage()
{
    puts("Usage: logClient <logserver_addr>");
    exit(2);
}

    void
runClient(CLIENT* clnt)
{
    char msg[MSG_SIZE];
    int logID;
    int* result;
    
		 /*
 	  * client opens a log
 	  */
    result = openlog_1("client", clnt);
    if (NULL == result) {
        clnt_perror(clnt, "openlog");
        return;
    }
    logID = *result;
    if (-1 == logID) {
        puts("Cannot open the log.");
        return;
    }
    
    while(1) {
        struct rpc_err e;
        
			 /*
		 	  * Client writes a message in the log
		 	  */
        puts("Enter a message in the log (\".\" to quit):");
        fgets(msg, MSG_SIZE, stdin);
        /* 
				* Remove trailing CR 
				*/
        msg[strlen(msg)-1] = 0;
        
        if (!strcmp(msg, ".")) break;
    
        if (writelog_1(logID, LOG_INFO, msg, clnt) == NULL) {
            clnt_perror(clnt, "writelog");
            return;
        }
    }
		 /*
		  * Client closes the log
		  */
    result = closelog_1(logID, clnt);
    if (NULL == result) {
        clnt_perror(clnt, "closelog");
        return;
    }
    logID = *result;
    if (-1 == logID) {
        puts("Cannot close the log.");
        return;
    }
}

    int
main(int argc, char* argv[])
{
    char* serv_addr;
    CLIENT* clnt;
    
    if (argc != 2) usage();

    serv_addr = argv[1];

    clnt = clnt_create(serv_addr, LOG, LOG_VERS1, "tcp");

    if (NULL == clnt) {
        clnt_pcreateerror("Cannot connect to log server");
        exit(1);
    }
    runClient(clnt);

    clnt_destroy(clnt);
}