[comp.lang.perl] easy-serv.pl : a simple server/client program library

sakoh@sraco2.us.sra.co.jp (Hiroshi &) (11/17/90)

Easy-serv library is a collection of util. routines which support to
build simple server/multi-client programs.

enjoy.

#!/bin/sh
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by sakoh@sra.co.jp on Fri Nov 16 18:08:29 MST 1990
# Contents:  README.eng easy-serv.man.eng easy-serv.pl easy-ipc.pl
 
echo x - README.eng
sed 's/^@//' > "README.eng" <<'@//E*O*F README.eng//'

README for easy-serv.pl			: Fri Nov  9 14:46:50 MST 1990

1. MOKUTEKI - (Purpose)

     Easy-serv is a set of routines written in PERL to support constructing
 server/client programs.
 Although it has a limited features only, it's still useful
 for experimental or simple applications.
 A server can serve multiple clients at the same time.
 Tcp protocol is used for the purpose.
 Someone might find some conceputual similarity with
 Sun's RPC (Remote Procedure Call).
 However there is no XDR (eXternal Data Representation) supports (sorry).

2. RIYOU JOUKEN TOU - (Copyright notice)


 Copyright 1990 Software Research Associates, Inc.

 Permission to use, copy, modify, distribute, and sell this software and its
 documentation for any purpose is hereby granted without fee, provided that
 the above copyright notice appear in all copies and that both that
 copyright notice and this permission notice appear in supporting
 documentation, and that the name of S.R.A. not be used in advertising or
 publicity pertaining to distribution of the software without specific,
 written prior permission.  S.R.A. makes no representations about the
 suitability of this software for any purpose.  It is provided "as is"
 without express or implied warranty.

 S.R.A. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL S.R.A.
 BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
 CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

 Author:  Sakoh, Hiroshi - Software Research Associates, Inc.
@//E*O*F README.eng//
chmod u=rw,g=rw,o=r README.eng
 
echo x - easy-serv.man.eng
sed 's/^@//' > "easy-serv.man.eng" <<'@//E*O*F easy-serv.man.eng//'
@.TH EASY-SERV l "Nov 1, 1990"
@.SH NAME
easy-serv \- simple server/multi-client program library in perl.
@.SH SYNOPSIS
@.br
@.B require """easy-serv.pl""
@.br
@.B "&register_serv(socket, port)"
@.br
@.B "&run_serv()"
@.br
@.B "&cleanup_serv()"
@.br
@.B "&register_client(socket,  host, port)"
@.br
@.B "&send_request(socket, request)"
@.br
@.SH DESCRIPTION
Easy-serv is a set of routines to support constructing
server/client programs.
Although it has a limited features only, it's still useful
for experimental or simple applications.
a server can serve multiple clients at the same time.
Tcp protocol is used for the purpose.
Someone might find some conceputual similarity with Sun's RPC
(Remote Procedure Call).
However there is no XDR (eXternal Data Representation) supports (sorry).
@.PP
Routines &register_serv(), &run_serv(), &cleanup_serv() are
used in server side.
The rest &register_client(), &send_request() are used in client side.
@.TP
@.B "&register_serv(SOCKET, PORT)"
creates a internet tcp/ip SOCKET and bind it to the specified PORT.
This SOCKET will be used as server socket.
You can specify PORT as number or a symbolic name
found in /etc/service.
@.TP
@.B "&run_serv()"
processes all requests from multiple clients.
Before calling this routine, you must call
&register_serv() to have a server socket.
Users must supply a routine named
@.B "&serv_body(REQUEST, SOCKET)"
which does actual services.
In REQUEST argument, you will find data from the current client.
And in SOCKET argument, you have the current client's socket name.
Each client's socket name is made to be unique within a server so that
you can distinguish clients within &serv_body() routine.
For example, if you make a server socket by  &register_serv(SERVER, 79999),
consequently each client's socket has a name like "SERVER..a", "SERVER..b" ..
and so on.
The return value of
&serv_body()
is sent back to the current client as it is.
See the example described below.
@.TP
@.B "&cleanup_serv()"
closes all sockets and exits.
This is used in &serv_boody() routine.
@.PP
The following routines are used in clients.
@.TP
@.B "&register_client(SOCKET, HOST, PORT)"
makes a client side SOCKET connecting to the specific PORT
on the specific HOST.
For HOST, you can supply a name found in /etc/hosts or
a internet address format like 11.22.33.44.
PORT's format is same as &register_serv().
@.TP
@.B "&send_request(SOCKET, REQUEST)"
sends an REQUEST to the corresponding server via SOCKET which
is created by &register_client() call.
It waits a reply from server and returns it.
@.PP
Easy-serv library routines rely on the routines defined in
easy-ipc.pl.
You might be able to build
more sophisticated applications by using them directly.
@.SH EXAMPLE
@.sp
A server
@.sp
@.nf
@.ft LP
#!/usr/local/bin/perl

require "easy-serv.pl";	# loading the library

&register_serv(SERVER, 7999);	# make a server socket on port 7999

&run_serv();	# dispatching (never returns)

sub serv_body {	# Users must supply this routine.
		# $_[0] : request data sent from a client
		# $_[1] : socket name corresponding to the current client

    print "client sock name=" . $_[1] . "\n";

    if ($_[0] eq "who") {
	$reply = `who`;
    } elsif ($_[0] eq "ps") {
	$reply = `ps`;
    } elsif ($_[0] eq "kill") {
	&cleanup_serv();
    } else {
	$reply =  "who  : who is on this host\\n" 
		. "ps   : show process\\n" 
		. "kill : kill server\\n" 
		. "help : show this message\\n";
    }
    $reply;	# This value is sent back to the current client
}

@.ft P
@.fi
@.sp
A client
@.sp
@.nf
@.ft LP
#!/usr/local/bin/perl

require "easy-serv.pl";	# loading the library

&register_client(CLIENT, "myhost", 7999);
			# connecting to port 7999 on "myhost"

$| = 1;
print "-> ";
while(print "-> ", <STDIN>) {
    chop;
    $reply = &send_request(CLIENT, $_); # send a request
    print $reply;			# print the reply
}
@.ft P
@.fi
@.SH FILES
/usr/local/lib/perl/easy-serv.pl : easy-serv library
@.br
/usr/local/lib/perl/easy-ipc.pl : low level util. library
@.br
/usr/local/lib/perl/sys/socket.ph (not included)
@.br
/usr/local/lib/perl/sys/fcntl.ph  (not included)
@.SH "IMPORTANT NOTE"
@.SH BUGS
Package isn't used (yet).
@.br
Tested only on perl v3.0 patchlevel > 37.
@.SH AUTHOR
Sakoh, Hiroshi: Software Research Associates, Inc.
@.br
sakoh@sra.co.jp or uunet!sra.co.jp!sakoh
@//E*O*F easy-serv.man.eng//
chmod u=rw,g=rw,o=r easy-serv.man.eng
 
echo x - easy-serv.pl
sed 's/^@//' > "easy-serv.pl" <<'@//E*O*F easy-serv.pl//'
######
#
#   EASY-SERV.PL
#       Copyright : Software Research Associates, Inc. 1990
#	Written by Hiroshi Sakoh (sakoh@sra.co.jp) on 11-01-90
#	Please distribute widely, but leave my name here.
#

require "sys/fcntl.ph";
require "easy-ipc.pl";
#
# &register_serv($name, $port)
#
# Registers a service port.
#
sub register_serv {
    &defserver($_[0], $_[1]);
    fcntl($_[0], &F_SETFL, &FNDELAY)         || die "fcntl: $!\n";
    $_eserv_generic{$_[0]} = $_[0]; # define generic socket
    $_eserv_sockets{$_[0]} = $_[0];
}
#
# &register_client($name, $host, $port)
#
# Registers a client port.
#
sub register_client {
    &defclient($_[0], $_[1], $_[2]);
    fcntl($_[0], &F_SETFL, &FNDELAY)         || die "fcntl: $!\n";
    $_eclient_sockets{$_[0]} = $_[0];
}

#
# &run_serv()
#
# Wait for a request, dispatch it.
# Sends back a reply to the requester.
#
sub run_serv {

    local($bytestoread, $packet, $actualread, $request, $reply);
    local($bytestowrite, $actualwrite);
    local($try);
    local($MAXTRY) = 1000;

    for (;;) {

	@avails = &selectsock(keys(_eserv_sockets)); # wait for a request

	nextavail: for(@avails) {
	    if (defined($_eserv_generic{$_})) { # got a request for connection
		&acceptsock($_eserv_newsock, $_);
		$_eserv_sockets{$_eserv_newsock} = $_eserv_newsock;
	    } else {
		if (!eof($_)) { # got an usual request
		    chop($bytestoread = <$_>);
			# The first packet should contain bytes to be read.
		    for ( $request = "" ; $bytestoread > 0 ;
				$bytestoread -= $actualread) {
			for ($actualread = $try = 0;
			     ($actualread == 0) && ($try < $MAXTRY); $try++) {
			    $actualread = read($_, $packet, $bytestoread);
			}
			if ($actualread == 0) {
			    print STDERR "Unexpected EOF\n";
			    close($_);
			    delete $_eserv_sockets{$_};
			    next nextavail;
			}
			$request .= $packet;
		    }
		    $reply = &serv_body($request, $_);
		    # I'd really like to use syswrite() below to
		    # avoid to be blocked. But it doesn't work. Why??????
		    print $_ length($reply) . "\n";
		    for ($bytestowrite = length($reply)
			; $bytestowrite > 0 ; $bytestowrite -= 1024) {
			print $_  substr($reply, 0, 1024);
			$reply = substr($reply, 1024);
		    }
		} else {	# got an eof message
		    close($_);
		    delete $_eserv_sockets{$_};
		}
	    }
	}
    }
}

#
# &send_request($socket, $request)
#
# Sends a request to a server, waits a reply from the server
# and returns it.
#
sub send_request {
    local($sock, $request) = @_;
    local($reply, $packet, $bytestoread, $actualread);
    local($bytestowrite);
    local($try);
    local($MAXTRY) = 1000;

    # I'd really like to use syswrite() below to
    # avoid to be blocked. But it doesn't work. Why??????
    print $_ length($request) . "\n";		# write length of data
    for ($bytestowrite = length($request)	# write data packets
	; $bytestowrite > 0 ; $bytestowrite -= 1024) {
	print $sock substr($request, 0, 1024);
	$reply = substr($request, 1024);
    }

    chop($bytestoread = <$sock>);
	# The first packet should contain bytes to read.
    for ( $reply = "" ; $bytestoread > 0 ; $bytestoread -= $actualread) {
	for ($actualread = $try = 0;
	     ($actualread == 0) && ($try < $MAXTRY); $try++) {
	    $actualread = read($_, $packet, $bytestoread);
	}
	if ($actualread == 0) {
	    print STDERR "Unexpected EOF\n";
	    close($sock);
	    return "";
	}
	$reply .= $packet;
    }
    $reply;
}

#
# &cleanup_serv()
#
# Closes down all server sockets and exits.
#
sub cleanup_serv {
    for(keys(_eserv_sockets)) {
	close($_);
    }
    exit;
}

1;
@//E*O*F easy-serv.pl//
chmod u=r,g=r,o=r easy-serv.pl
 
echo x - easy-ipc.pl
sed 's/^@//' > "easy-ipc.pl" <<'@//E*O*F easy-ipc.pl//'
######
#
#   EASY-IPC.PL
#       Copyright : Software Research Associates, Inc. 1990
#	Written by Hiroshi Sakoh (sakoh@sra.co.jp) on 10-25-90
#	Please distribute widely, but leave my name here.
#

require "sys/socket.ph";
#
# &defserver(SOCKET, $port);
#
# Returns a server socket ready for acceptsock()
#
sub defserver { # arg0 == socket, arg1 == port
    local($this, $oldfh);
    local($port) = $_[1];
    local($name, $aliases, $proto);
    local($sockaddr_t) = 'S n a4 x8';

    ($name, $aliases, $port, $proto) = getservbyname($port, "tcp")
					unless $port =~ /^\d+$/;
    socket($_[0], &PF_INET, &SOCK_STREAM, 0) || die "socket: $!\n";
    $this = pack($sockaddr_t, &PF_INET, $port, "\0\0\0\0");
    bind($_[0], $this) || die "bind: $!\n";
    listen($_[0], 5);
    $oldfh = select($_[0]) ; $| = 1; select($oldfh);
}

#
# &defclient(SOCKET, $host, $port)
#
# Returns a connected client socket.
#
sub defclient { # arg0 == socket, arg1 == host, arg2 == port
    local($that, $thataddr, $oldfh);
    local($port) = $_[2];
    local($name, $aliases, $proto);
    local($sockaddr_t) = 'S n a4 x8';

    ($name, $aliases, $port, $proto) = getservbyname($port, "tcp")
					unless $port =~ /^\d+$/;
    if ($_[1] =~ /^\d+\.\d+\.\d+\.\d+$/) {
	$thataddr = &inet_addr($_[1]);
    } else {
	($name, $aliases, $type, $len, $thataddr) = gethostbyname($_[1]);
    }
    $that = pack($sockaddr_t, &PF_INET, $port, $thataddr);
    socket($_[0], &PF_INET, &SOCK_STREAM, 0) || die "socket: $!\n";
    connect($_[0], $that)                    || die "connect: $!\n";
    $oldfh = select($_[0]) ; $| = 1; select($oldfh);
}

$__EIPCSno__ = 'a';
#
# &acceptsock($newsock, $serversock)
#
# Returns a bind socket which is derived from a generic server socket
#
sub acceptsock {
    local($addr);
    local($oldfh);

    $_[0] = $_[1] . ".." . $__EIPCSno__ ++; # make it unique
    $addr = accept($_[0], $_[1]) || die "accept: $!\n";
    $oldfh = select($_[0]) ; $| = 1; select($oldfh);
    $addr;
}

#
# &selectsock(@sockets)
#
# Returns available sockets for read (blocks)
#
sub selectsock {
    &selectsock_with_timeout(undef, @_);
}

#
# &selectsock_non_block(@sockets)
#
# Returns available sockets for read (doesn't block)
#
sub selectsock_non_block {
    &selectsock_with_timeout(0, @_);
}

#
# &selectsock_with_timeout($timeout, @sockets)
#
# Returns available sockets for read (with timeout)
#
sub selectsock_with_timeout {
    local($nfound, $timeleft);
    local($rin, $rout, $win, $wout, $eout, $ein);
    local($timeout) = shift;

    $rin = &setsockbits(@_);
    ($nfound, $timeleft) = select($rout=$rin, $wout=$win, $eout=$ein, $timeout);

    grep(vec($rout,fileno($_),1),@_);
}

#
# &setsockbits(@sockets)
#
# Returns a bitvector corresponding to sockets list
#
sub setsockbits{
    local(@fhs) = @_;
    local($bits);
    for (@fhs) {
        vec($bits, fileno($_), 1) = 1;
    }
    $bits;
}

#
# &inet_addr($addr)
#
# Converts an internet dot notation (##.##.##.##) to an internet address
#
sub inet_addr {
    pack("C4", split(/\./, $_[0]));
}

1;
@//E*O*F easy-ipc.pl//
chmod u=r,g=r,o=r easy-ipc.pl
 
exit 0
--
 sakoh@sra.co.jp
"Whereof one cannot speak, thereof one must remain silent."  ---Wittgenstein
"Sometimes noise is significant."                            ---William Hewlett