Universität Ulm -Sektion Angewandte Informationsverarbeitung 7.Übungsblatt (15.06.99 bis 29.06.99) zur Vorlesung Systemnahe Software II (SS 99)




Aufgabe 1 (20 Punkte)

Implementieren Sie eine Client/Server-Anwendung mit Hilfe der Berkeley-Socket-Schnittstelle, die einen Kopierdienst für reguläre Dateien zwischen zwei Prozessen auf der Basis von TCP/IP realisiert.

Im Vergleich zu Übungsblatt 5 (FIFOs) müssen sich die Client- und die Server-Anwendung hier nicht notwendigerweise auf dem selben Rechnern befinden.

Das Anwendungsprotokoll sei folgendermaßen vereinbart:


   #define PUT     'p'
   #define GET     'g'
   #define ERROR   'e'

   typedef struct {
      char opcode;          /* p / g / e         */
      char file[256];       /* file name         */
      int  error;           /* error code        */
      int  length;          /* length of file    */
   } PROTO;
   #define PROTO_SIZE sizeof(PROTO)

Wird eine Datei von der Client-Anwendung an den Server transferiert, so enthält die Komponente opcode das Kommando PUT, die Kopmponente file den Dateinamen, der Wert von error ist irrelevant und die Komponente length enthält die Dateilänge in Bytes. Der Dateiinhalt folgt unmittelbar dem sogenannten Protokollkopf.

Beim Kopieren in umgekehrter Richtung ist das Kommando GET anzugeben, wobei die Dateilänge in dieser Anfrage ebenfalls irrelevant ist. Als Antwort liefert der Server im Fehlerfall in der Komponente opcode den Wert ERROR und in error einen entsprechenden Fehlerwert zurück. Im Erfolgsfall bleibt der Wert von opcode und file jeweils unverändert. Ist das Kommando gleich GET, so enthält length die Dateilänge des als Resultat der Anfrage unmittelbar nach dem Protokollkopf zurückgelieferten Dateiinhalts.

Die Aufrufsyntax des Copy-Servers sei


   copyserver [ port ]

und die des Copy-Clients


   copyclient [ -h hostname ] [ -p port ] ( p | g ) src-file dst-file

Die sogenannten Portnummern (optionales Kommandoargument port), welche die bereitgestellten Dienste eines Rechners repräsentieren, müssen auf jedem Rechner eindeutig sein. Der zulässige Wertebereich ist [0..65535], der empfohlene Wertebereich ist [5000..65535]. Verwenden Sie als Voreinstellung z.B. die Portnummer 20000 + x, wobei x Ihrer Benutzer-Identnummer entspricht (getuid()). Bei der expliziten Angabe einer Portnummer soll Ihr voreingestellter Wert überdefiniert werden.

Bleibt in der Client-Anwendung das optionale Kommandoargument hostname unspezifiziert, so soll der Kopierdienst auf dem lokalen Rechner (Hostname localhost) verwendet werden.

Implementieren Sie Ihren Copy-Server als sogenannten concurrent server, indem die Server-Anwendung jede Anfrage eines Clients (das Kopieren einer Datei) in einem separaten Prozeß verarbeitet.

Das Prinzip der zu realisierenden Server-Anwendung zeigt das folgende Programmfragment:


   int               sd, cd, len;
   struct sockaddr   addr;

   signal(SIGPIPE, SIG_IGN);
   signal(SIGCHLD, sigchld);

   getaddr(hostname, port, &addr, &len);       /* ist bereitgestellt */

   sd = socket(PF_INET, SOCK_STREAM, 0);

   soreuseaddr(sd);                            /* ist bereitgestellt */

   bind(sd, addr, len);

   listen(sd, SOMAXCONN);

   for(;;) {
      cd = accept(sd, NULL, NULL);

      switch(fork()) {
      case -1:
         perror("fork()"); exit(1);
      case 0:
         close(sd);
         copy(cd);             /* Implementierung des Kopierdienstes */
         _exit(0);
      default:
         close(cd);
      }
   }


Die installierte Signalbehandlungsfunktion sigchld() für das SIGCHLD-Signal zur Vermeidung von Zombie-Prozessen "muß" etwa folgendermaßen implementiert werden -- Warum? Lesen Sie im Manual nach und begründen Sie die Funktionsweise Ihrem Tutor.


   static void sigchld(int sig)
   {
      int s_errno = errno;

      for(errno = 0; waitpid(-1, NULL, WNOHANG) > 0 || errno == EINTR; errno = 0)
         ;

      errno = s_errno;
   }


Das Prinzip einer typischen Client-Anwendung zeigt der folgende Programmcode:


   int               sd, len;
   struct sockaddr   addr;

   getaddr(hostname, port, &addr, &len);       /* ist bereitgestellt */

   sd = socket(PF_INET, SOCK_STREAM, 0);

   connect(sd, addr, len);

   copy(???);


Die beiden Funktionen getaddr() und soreuseaddr() liegen unter
ftp://ftp.mathematik.uni-ulm.de/pub/vorlesungen/ss99/soft/7/.

Machen Sie sich mit den Socket-Funktionen ein wenig vertraut (Manual).
Weitere Hinweise in der Vorlesung und den Übungen.