LEZIONE del 4 Dicembre

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
#define       FALSE         0

void Print(int L, char msg[MAXLEN]) {
  int i;
  printf("'");
  for (i = 0; i < L; i++) {
    if ((msg[i] >= 32)&&(msg[i] < 127)) printf("%c", msg[i]);
    else printf("[%d]", msg[i]);}
  printf("'\n"); }

void sPrint(int Channel, char msg[MAXLEN], int L, int K) {
  char  W[MAXLEN], Bu[MAXLEN];
  strncpy(W, msg, L-2);
  W[L-2] = '\0'; W[L-1] = '\0';
  sprintf(Bu, "L[%d] = %s\n", K, W);
  write(Channel, Bu, strlen(Bu));
}

void guess_action(int in, int out) {

  char  cmd[MAXLEN];
  int   nLine = 0, count = 1;
  int   Running;
  int   LineSize;

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

  Running = TRUE;
  while (Running) {
    LineSize = read(in, cmd, MAXLEN);
    Print(LineSize - 2, cmd);
    nLine++;
    if (cmd[1] == ' ') {
      switch (cmd[0]) {
        case 'C' :
          count = count + 5;
          break;
        case 'Q' :
          count = MAXCOUNT - 1;
          break; }}
    sPrint(out, cmd, LineSize, nLine);
    count++;
    if (count == MAXCOUNT) Running = FALSE; }
  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 questo secondo esempio, il file testuale contenente le parole incognite deve essere realizzato con una lista di stringhe che, per questo devono essere quotate e 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.

Prima di terminare la discussione si consideri, al posto del ben strutturato client TELNET, il più semplice client riportato di seguito

/*
*  xtel.c
*     eXecute TErminal emuLation
*/
#include	<stdio.h>
#include	<string.h>
#include	<netdb.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	<stdlib.h>


#define		BUFLEN		512
#define		NAMESIZE	120

#define		FALSE		0
#define		TRUE		1

char *buf[BUFLEN];

int readSocket(int sd, char *buf) {
  int len;
  int Running;

  Running = TRUE;
  len = BUFLEN + 1;
  while ((len >= BUFLEN) && (Running)) {
    len = read(sd, buf, BUFLEN);
    write(1, buf, len);
    buf[BUFLEN - 1] = '\0';
    Running = (strstr(buf, "Bye") == NULL); }
  return Running; }


int main(int argc, char **argv) {

   unsigned int portno;
   char hostname[NAMESIZE];
   struct hostent *host;
   struct sockaddr_in sin;
   int sd;
   char buf[BUFLEN];
   int len;

   if (argc < 3) {
     fprintf(stderr, "Sintassi: %s <Host> <Port>\n", argv[0]);
     exit(1); }

   strcpy(hostname, argv[1]);
   host = gethostbyname(hostname);
   portno = atoi(argv[2]);

   sd = socket(AF_INET, SOCK_STREAM, 0);  /* init socket descriptor */

   memcpy(&sin.sin_addr.s_addr, host->h_addr, host->h_length);
   sin.sin_family = AF_INET;
   sin.sin_port = htons(portno);

   if (connect(sd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
     perror("connecting");
     exit(2);
   }

   while (TRUE) {
     printf("\n\n");
     if (!readSocket(sd, buf)) break;
     fgets(buf, BUFLEN, stdin);
     write(sd, buf, strlen(buf));
     sleep(1);
     }
   close(sd);
   }

e se ne verifichi i limiti d'impiego. Si analizzi il suo funzionamento e si determinino le possibili estensioni per poter essere utilizzato al posto del già discusso TELNET.