[comp.sys.sun] RPC server -- How to detect death of client?

anund@idt.unit.no (Anund Lie) (03/19/91)

When running Sun RPC over TCP (or other connection-oriented services, for
that matter), how can the server detect that a client process has died?
(More precisely: That no process has the other end of the connection
open.)  

On the socket level, this will be detected by a read() on the socket
returning 0 (end-of-file).  The best thing would be to be able to register
a procedure or in some other way control the action to be performed when
RPC detects EOF on a socket.  (It has to detect EOF anyway and close its
end of the socket, hasn't it?)

The manuals does not mention this, but /usr/include/rpc/svc.h (SunOS 4.1)
defines the operation SVC_STAT().  Presumably the (pointer to) the
transport handle could be saved at the first call from a new client, and
the server periodically SVC_STAT() the set of active handles.  Is this a
reasonable thing to do?  There does not seem to be any guarantee that
transport handles are unique over different RPC calls, so that it is OK to
store them in this way.

Anund Lie
Email: anund@idt.unit.no       (Internet)

mh@roger.imsd.contel.com (Mike Hoegeman) (03/23/91)

In article <1972@brchh104.bnr.ca> anund@idt.unit.no (Anund Lie) writes:
>When running Sun RPC over TCP (or other connection-oriented services, for
>that matter), how can the server detect that a client process has died?
>(More precisely: That no process has the other end of the connection
>open.)  

I've seen a couple of questions like this so i'll post below an example of
how I deal with client management in a tcp based RPC service. The comments
explain things fairly well but if anyone has any questions go ahead and
drop me a line

mike hoegeman, mh@awds.imsd.contel.com

------ example below -------

#include <stdio.h>
#include <rpc/rpc.h>
#include "db.h" /* your service.h goes here */

#define LogM fprintf
#define AS_ERROR stderr
#define AS_DEBUG stderr
#define LogInit(X) 

#include <sys/errno.h>

extern void db_program_1();

SVCXPRT *transp;

main()
{
    LogInit();
    (void) pmap_unset(DB_PROGRAM, DB_VERSION);

    if (!svc_register(transp,
	DB_PROGRAM, DB_VERSION, db_program_1, IPPROTO_UDP))
    {
	fprintf(stderr, "unable to register (DB_PROGRAM, DB_VERSION, udp).\n");
	exit(1);
    }
    transp = svctcp_create(RPC_ANYSOCK, 0, 0);
    if (transp == NULL)
    {
	(void) fprintf(stderr, "cannot create tcp service.\n");
	exit(1);
    }
    if (!svc_register(transp,
	DB_PROGRAM, DB_VERSION, db_program_1, IPPROTO_TCP))
    {
	fprintf(stderr, "unable to register (DB_PROGRAM, DB_VERSION, tcp).\n");
	exit(1);
    }
    run(); /* like svc_run() but does client management callbacks */
    (void) fprintf(stderr, "run returned\n");
    exit(1);
}


run()
{
    extern int errno;

#ifdef FD_SETSIZE
    fd_set  readfds;
    static fd_set  oldfds;

    oldfds = svc_fdset;
#else
    int     readfds;
    static int     oldfds;

    oldfds = svc_fds;
#endif	/* def FD_SETSIZE */


    for (;;)
    {
	register int setsize;
	setsize = _rpc_dtablesize();

#ifdef FD_SETSIZE
	readfds = svc_fdset;
#else
	readfds = svc_fds;
#endif	/* def FD_SETSIZE */

	switch (select(setsize, &readfds, (int *) 0, (int *) 0,
	    (struct timeval *) 0))
	{
	case -1:
	    if (errno == EINTR)
		continue;
	    perror("svc_run: - select failed");
	    return;
	case 0:
	    continue;
	default:
	    {
		/*
		   at the start of each go round in the select loop we can
		   detect when a new connection has been made or broken this is
		   were we do our client management
		*/

		register int chunk;
		register int bit;
		register unsigned long mask;
		register unsigned long *maskp;
		int fd;

		/* for each bit in the new mask */
		maskp = (unsigned long *) (svc_fdset.fds_bits);
		for (chunk = 0; chunk < setsize; chunk += NFDBITS)
		    for (mask = *maskp++;
			bit = ffs(mask); mask ^= (1 << (bit - 1)))
		    {
			fd = chunk+bit-1;
			/*
			   if fd is not in the old mask, then we have a
			   new connection
			*/

			if (FD_ISSET(fd, &svc_fdset) && !FD_ISSET(fd, &oldfds))
			    if (c_add(fd))
				LogM(AS_ERROR, "add error");
		    }

		/* for each bit in the old mask... */
		maskp = (unsigned long *) (oldfds.fds_bits);
		for (chunk = 0; chunk < setsize; chunk += NFDBITS)
		    for (mask = *maskp++;
			bit = ffs(mask); mask ^= (1 << (bit - 1)))
		    {
			fd = chunk+bit-1;
			/* 
			    if fd is not in the new mask then we have lost
			    a connection
			*/
			if (FD_ISSET(fd, &oldfds) && !FD_ISSET(fd, &svc_fdset))
			    if (c_delete(fd))
				LogM(AS_ERROR, "delete error");
		    }

		oldfds = svc_fdset; /* should have a better scheme for
					copying fd sets here */

		svc_getreqset(&readfds);
	    }
	}
    }
}

/* client 'close' callback */
c_delete(n)
int n;
{
    LogM(AS_DEBUG,"c_delete: %d <-----\n", n);
    return 0;
}

/* client 'open' callback */
c_add(n)
int n;
{
    LogM(AS_DEBUG,"c_add: %d <-----\n", n);
    return 0;
}