LEZIONE del 5 Marzo

Nella lezione precedente è stato presentato un esempio di server iterativo costituito da una routine principale main che rappresenta l'ossatura del server e la subroutine guess_action, da realizzarsi a parte nel file action.c, che ne implementa la specifica funzionalità. Per effettuare la compilazione bisogna utilizzare il comando

gcc -o server -lnsl server.c action.c

dove action.c è il file che contiene il codice della subroutine. Per l'utilizzo del server procedere nel seguente modo.

A questo punto il server produrrà un "banner" di avvenuta connessione permettendo all'utente TELNET di dialogare con la routine eseguita remotamente dal server.

Implementazione della Routine eseguita dal Server

L'implementazione della routine eseguita dal server si basa sul tipo particolare di protocollo che realizza lo scambio di informazioni con il client. Nel caso specifico, il protocollo utilizzato è uno dei protocolli applicativi per internet più semplici e più diffusi, e prende il nome di protocollo TELNET. In esso il flusso dei dati è ottenuto con uno stream bidirezionale che simula il funzionamento di un terminale seriale alfanumerico ed è immediatamente implementabile sul protocollo TCP, ottenuto con le socket stream.

Se si vuole che una routine possa utilizzare direttamente questo tipo di protocollo è sufficiente definire come parametri di comunicazione le due variabili intere in e out, la prima per sostituire lo standard input "stdin" e la seconda per lo standard output "stdout" ed applicare su di esse le procedure

Si tenga presente che tutte le operazioni di lettura e/o scrittura devono essere realizzate esplicitamente su buffer che non siano quelli definiti implicitamente per lo standard input e output, altrimenti i messaggi non potranno essere inoltrati dalla socket.

LA SUBROUTINE GUESS_ACTION

Il primo esempio di routine caratterizza un semplice programma che copia la riga corrente dell'input (in forma di stringa) nella riga di output, per un numero massimo MAXCOUNT di copie, a meno che non vengano introdotti i comandi

Di seguito il codice C.

#include   <stdio.h>
#include   <stdlib.h>
#include   <string.h>

#define    MAXLEN    80
#define    MAXCOUNT  10
#define    TRUE      1

void guess_action(int in, int out)
{
  char  cmd[MAXLEN], msg[MAXLEN];
  int   count = 0;

  sprintf(msg, "benvenuto in questo server\n\n");
  write(out, msg, strlen(msg));

  while (TRUE) {
    read(in, cmd, MAXLEN);
    switch (cmd[0]) {
      case 'C' :
        count = count - 5;
        break;
      case 'Q' :
        count = MAXCOUNT - 1;
        break;
      default :
        strcpy(cmd, msg);
        write(out, msg, strlen(msg));
    }
    printf("count %d\n", count);
    count++;
    if (count == MAXCOUNT) exit(0);
  }
}

Il secondo esempio, invece, mostra il caso di un programma che simula il gioco di indovinare una parola proposta dal server, a partire da un database di parole predefinito e selezionata casualmente, mediante una serie ripetuta di proposte del cliente, fatte digitando un carattere da tastiera. Ovviamente, è consentito un limite massimo di errori.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

char *word[] = {
#include "words"
};

#define NUM_OF_WORDS (sizeof(word)/sizeof(word[0]))
#define MAXLEN   80
#define MAXTOSS  6

void guess_action(int in, int out)
{
  char *whole,
        part[MAXLEN],     /* parola parziale */
        guess[MAXLEN],
        msg[MAXLEN];

  int toss = MAXTOSS;
  int game = 'I';         /* incomplete */

  int i, OK, wsize;
  char hostname[MAXLEN];
  unsigned seed;

  gethostname(hostname, MAXLEN);
  sprintf(msg, "Looking for a word on host %s:\n\n", hostname);
  write(out,msg,strlen(msg));

          /* scegli una parola a caso */

  seed = time(NULL);
  srand(seed);
  whole = word[rand() % NUM_OF_WORDS];
  wsize = strlen(whole);

  printf("guess server chose word: %s\n", whole);

          /* all'inizio non ci sono lettere indovinate */

  for ( i=0; i < wsize; i++) part[i] = '-';
  part[wsize]='\0';


  sprintf(msg, " %s  %d\n", part, toss);
  write(out, msg, strlen(msg));

          /* leggi la proposta del cliente */

  while (game == 'I') {
    while (read(in, guess, MAXLEN) < 0) {
      if (errno != EINTR) {
	perror("while reading");
	exit(4);                        /* se altro errore termina */
      }
      printf("Restarting read\n");      /* se interrotto da una signal */
    }

         /* aggiorna la parola parziale */

    OK = 0;
    for (i=0; i ;lt; wsize; i++) {
      if (guess[0] == whole[i]) {
	OK = 1;
	part[i] = whole[i];
      }
    }

    if (! OK) toss--;
    if (strcmp(whole, part) == 0) game = 'W';	  /* il cliente vince */
      else if (toss == 0) {
             game = 'L';	                  /* il cliente perde */
             strcpy(part, whole);                 /* mostra la soluzione */
            }
    sprintf(msg, " %s  %d\n", part, toss);
    write(out, msg, strlen(msg));
  }
}
In quest'ultimo esempio proposto, il file testuale contenente le parole incognite deve essere realizzato con una lista di stringhe che, per questo devono essere quotate, separate l'una dall'altra dalla virgola. Si consiglia, per semplicità, di utilizzare una riga diversa per ciascuna parola.

Come si può facilmente verificare il server rimane in ascolto di nuove richieste sulla porta port passata come primo ed unico parametro nella linea di comando che attiva il server stesso. Ogni qualvolta arriva una nuova richiesta di connessione, da parte di un client TELNET, che viene accettata, allora il server viene clonato e rimane attivo per tutto il tempo necessario al processamento della richiesta. Poichè il server viene lanciato e bloccato dalla shell una volta per tutte, è comodo disporre di uno shell script specifico che compie questo tipo di operazioni. Nel seguito viene proposto un possibile esempio di tale file

#!/bin/bash
#
# hangup:    Start/Stop/Status the Hang Up server
#

SP="         "
PORT=48000
LOGFILE="hangup.log"
SERVER="HangUp"
TheProg="hangup"

function start() {
  echo -n "Starting the Hang Up server at the port $PORT: "
  $SERVER $PORT > $LOGFILE &
  RETVAL=$?
  [ $RETVAL -eq 0 ] && echo "${SP}[  OK  ]"
  return $RETVAL
}

function status() {
  PSB=(`ps ax | grep -e "$SERVER" | grep -v "grep"`)
  if [ "${PSB[0]}" != "" ]; then
    echo "process $SERVER (pid ${PSB[0]}) is running"
    RETVAL=$?
  else
    echo "process $SERVER is stopped"
    RETVAL=1
  fi
  return $RETVAL
}

function stop() {
  echo -n "Shutting down hangup server: "
  PSB=(`ps ax | grep -e "$SERVER" | grep -v "grep"`)
  if [ "${PSB[0]}" != "" ]; then 
    kill -9 ${PSB[0]}
    RETVAL=$?
    [ $RETVAL -eq 0 ] && echo "${SP}${SP}${SP}[  OK  ]"
  else
    RETVAL=1
    echo  "${SP}${SP}[failed]"
  fi
  return $RETVAL
}

case "$#" in
  0) echo "*** Usage: $theProg {start|stop|status}" ;
     exit 2 ;;
  1) export ACTION="$1" ;;
esac

case "$ACTION" in
  start)
        start ;
        RETVAL=$? ;;

  status)
        status ;
        RETVAL=$? ;;

  stop)
        stop ;
        RETVAL=$? ;;
  *)
       echo "*** Usage: $theProg {start|stop|status}"
       RETVAL=1 ;;
  esac
  exit $RETVAL
}

con il compito di fornire all'utente i comandi di stop, start e status del servizio stesso. Nello shell script in questione si suppone che il servizio si chiami HangUp, venga lanciato sulla porta 48000 e i messaggi prodotti siano raccolti nel file di log hangup.log.