<-- Studium  

Netzwerke und Protokolle

Labor 1Hallo Welt
Labor 2Kopieren im Dateisystem und Fehlerbehandlung
Labor 3Signalverarbeitung
Labor 4Benutzereingaben abfangen
Labor 5fork()
Labor 6Abschlussuebung: Kommunikation mit named pipes



Labor 1:

Hallo Welt mit write (anstatt printf):
 int main ()
{ // Hallo und newline wird in das Character Array h geschrieben char h[] = "Hallo\n"; // Eine Variable um den Rueckgabewert der write Funktion aufzunehmen wird deklariert int retval; // Aufruf der write Funktion: // 1 ist file descriptor fuer die Konsole (0=input, 1=output, 2=error) // h ist was ge-write-ed wird, unsere Variable in der Hallo steht // 6 ist die Anzahl der Zeichen die geschrieben werden (5 fuer Hallo, 1 fuer newline) retval = write(1, h, 6); }

 
"Normales" printf Hallo Welt (mit korrekter Ueber und Rueckgabe):
#include < stdio.h >

int main (int argc, char *argv[])
{
  printf("Hallo\n");
  return(0);
}

Labor 2:

Ein einfaches Kopierprogramm mit read und write:
/*
 *
 *	Beispielprogramm fuer Dateideskriptoren
 *
 *      kompilieren und linken mit   cc -o copy copy.c
 *
 *      ausfuehren     ./copy Paramater1 Parameter2
 *
 *      wobei Parameter1 und Parameter2 Dateiname oder
 *      Geraetename sein koennen.
 */

// Standardincludes

#include < sys/types.h >
#include < fcntl.h >
#include < unistd.h >

//Wird zum Verwenden der errno Variable benoetigt
#include < errno.h >

#define BUF_SIZE        4096
#define MODE		0644

// Uebergabe der Parameter ueber argc und argv.
// in argc steht die Anzahl der Uebergabeparameter plus 1
// denn in argv[0] steht der Programmname, in argv[1] der
// erste Uebergabeparameter, in argv[2] der zweite usw.

// Systemweite Fehlervariable, erhaelt vom BS den Fehlerwert
int	errno;

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

//	die Dateidescriptoren sind einfache Interger-Variablen.
//	Sie beschreiben im Endeffekt eine logische Kanalnummer
// 	(Kanalnummern 0,1,2 sind fest zugewiesen stdin, stdou,sterr)

   int     src, dst, in, out;
   char    buf[BUF_SIZE];

//	Stimmt die Anzahl der Uebergabeparameter ?

   if (argc !=3) exit(1);

//	Oeffnen der ersten "Datei" Rueckgabewert ist das Filehandle == Kanalnummer
//	Ubergeben wird der "Dateiname" (String) und der Modus (hier: nur lesend)
//	siehe auch    man open

   src = open(argv[1], O_RDONLY);

//	Im Gutfall bekommt man einen Wert groesser/gleich NULL zurueck
//	Im Fehlerfall bekommt man als Rueckgabewert -1

   if (src < 0){
      perror(" ");
      exit(2);
   }

//	Oeffnen der zweiten "Datei" Rueckgabewert ist das Filehandle == Kanalnummer
//	Uebergeben wird der "Dateiname" (String) und der Modus (hier: falls diese noch
//	nicht existiert wird sie angelegt )

   dst = creat(argv[2], MODE);

//	Im Gutfall bekommt man einen Wert groesser/gleich NULL zurueck
//	Im Fehlerfall bekommt man als Rueckgabewert -1

	//printf("\nsrc: %d, dst: %d\n",src,dst);

   if (dst < 0) {

	// Fehler ausgeben
      perror(" ");
      exit(3);
   }

   while (1){

//		Lesen von der ersten Kanalnummer. Der read-Aufruf blockiert so lange
//		bis in dem Kanal Zeichen kommen und liest dann bis max. BUF_SIZE
//		Zeichen aus dem Kanal und kopiert diese in das Feld buf.
//		Als Rueckgabewert steht in der Variablen in dann die tatsaechlich
//		gelesene Anzahl von Bytes
//		siehe auch    man 2 read

      in = read(src, buf, BUF_SIZE);

//		Ist der Rueckgabewert -1 dann ist ein Fehler aufgetreten

      if (in <= 0) break;

//		Schreiben in den zweiten Kanal.
//		Als Rueckgabewert steht in der Variablen in dann die tatsaechlich
//		geschriebene Anzahl von Bytes
//		siehe auch    man 2 write
 
      out = write (dst, buf, in);

//		Ist der Rueckgabewert -1 dann ist ein Fehler aufgetreten

      if (out  <= 0) break;

   }

//	Schliesen der Kanaele. Beim exit() schliesst das Betriebssystem diese automatisch


   close(src);
   close(dst);

   exit(0);
}

 
Beispielprogramm zur Fehlerbehandlung:
#include < stdio.h >
#include < errno.h >

// 	Systemweite Fehlervariable. Wenn ein Fehler bei einem
//	Funktionsaufruf auftritt, so setzt das Bestriebssystem
//	diese Variable auf einen Wert (errno.h) und mit dem 
//	Aufruf perror() kann der zugehoerige Fehlertext auf den
//	Bildschirm ausgegeben werden.

int	errno;

main(argc, argv)

int argc;
char *argv[];

{
	int i;

	for (i=0; i < 128; i++)
	{
		fprintf(stderr, "%3d",i);
//		Hier wird die Fehlervariable "kuenstlich" gesetzt

		errno = i;
//		Ausgabe der Fehlertextes (vgl. errno.h)

		perror (" ");
	}
	exit(0);
}

 

Labor 3:

Prozesse, Signalverarbeitung:
pid = process id
ppid = parent process id
fork = "gabel" ezeugt prozesskopie
kill -INT pid = Signal SIGINT, Signalnummer 2
 
Neuer Prozess starten heisst erstmal alten Prozess kopieren und dann aendern
Interprozesskomunikation = Signal schicken
 
/*
 * Arbeiten mit Signalen.
 *
 */

#include < stdio.h >
#include < signal.h >

//neu: 3 folgende includes werden für open() gebraucht
#include < sys/types.h >
#include < sys/stat.h >
#include < fcntl.h >

static	int	sigrecv = 0;		// Zaehler fuer die empfangenen Signale
void		sigint();		// Signalbehandlungsroutiune
void		sighup();		// Signalbehandlungsroutiune

//neu: "globale Variable buf in der dann nach dem Aufruf von einlesen() der Text aus der Datei steht
char  buf[4096];
//neu: Funktion einlesen(). 
//neu: Datei abc zum lesen öffnen. Variablen src, in werden zum einlesen benötigt. Filedescriptor ist src
void einlesen(){
	int src, in;
	
	src = open("abc", O_RDONLY);
	in = read(src, buf, 4096);
}


main (argc, argv)

int	argc;
char	argv[];

{
	int   rc = 0;

	//neu: Funktion beim starten des Programms aufrufen um die Textdatei einzulesen
	einlesen();

//	Signalhandler initialisieren - Beim Empfang der spezifizierten
//      Signale wird in die entsprechende Routine gesprungen. 
//	Die Werte der Signale stehen in signal.h 
//	Signal SIGKILL kann nicht abgefangen werden

	signal(SIGINT, sigint);
	signal(SIGHUP, sighup);

        printf("My PID: %d My PPID: %d\n",getpid(), getppid());

//	Warten auf die Signale

	for (;;)
	{
		//neu: Statt dieser "Waiting for signal..." wird jetzt
		//der Inhalt der Datei abc ausgegeben den unsere
		//Funktion in buf geschrieben hat
		printf("Waiting for Signal ..\n");
		printf(buf);
		sleep(5);
	}
	exit(rc);
}


//	Wenn das Signal SIGINT (Nr. 2) an diesen Prozess geschickt wird,
//	dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
//	Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
//	sichert den Zustand so dass mit dem return wieder an der Stelle
//	weitergemacht wird an der Prozess war als das Signal empfangen 
//	wurde.

void sigint(signo)

int	signo;
{
	sigrecv++;
	printf("Signal Nr. %d received by sigint, %d signal\n", signo,sigrecv);

	//neu: Wenn dieses Signal ermpfancen wird, z.B. durch [Strg] + [c]
	//drücken wird die Textdatei erneut eingelesen
	einlesen();

	fflush (stdout);
}

//	Wenn das Signal SIGHUP (Nr. 1) an diesen Prozess geschickt wird,
//	dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
//	Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
//	sichert den Zustand so dass mit dem return wieder an der Stelle
//	weitergemacht wird an der Prozess war als das Signal empfangen 
//	wurde.

void sighup(signo)

int	signo;
{
	sigrecv++;
	printf("Signal Nr. %d received by sighup, %d signal\n", signo,sigrecv);
	fflush (stdout);
	exit(1);
}

Die Datei abc ist eine ascii Datei und kann mit beliebigen (soso ;-) Inhalt angelegt werden.
 
Unglaublicher Komfort mit Praeprozessor power:
/*
 * Signal.h
 *
 */

#define SIGHUP           1
#define SIGINT           2
#define SIGQUIT          3
#define SIGILL           4
#define SIGTRAP          5
#define SIGABRT          6
#define SIGIOT           6
#define SIGBUS           7
#define SIGFPE           8
#define SIGKILL          9
#define SIGUSR1         10
#define SIGSEGV         11
#define SIGUSR2         12
#define SIGPIPE         13
#define SIGALRM         14
#define SIGTERM         15
#define SIGSTKFLT       16
#define SIGCHLD         17
#define SIGCONT         18
#define SIGSTOP         19
#define SIGTSTP         20
#define SIGTTIN         21
#define SIGTTOU         22
#define SIGURG          23
#define SIGXCPU         24
#define SIGXFSZ         25
#define SIGVTALRM       26
#define SIGPROF         27
#define SIGWINCH        28
#define SIGIO           29
#define SIGPOLL         SIGIO
/*
#define SIGLOST         29
*/
#define SIGPWR          30
#define SIGSYS          31
#define SIGUNUSED       31

 

Labor 4:

[Strg] + [c] abfangen
#include  < stdio.h >
#include  < signal.h >

static	int	sigrecv = 0;		// Zaehler fuer die empfangenen Signale
void		sigint();		// Signalbehandlungsroutiune
void		sighup();		// Signalbehandlungsroutiune
int		strgc = 0;

void irhandler()
{
	char eing;
	fflush (stdin);
	printf("Wollen sie wirklich beenden?\n");
	scanf("%c", &eing);

	if (eing == 'y'){
		printf("Beenden..\n");
		sleep(1);
		exit(1);
	}

	if (eing == 'n'){
		printf("Weiter..\n");
		sleep(1);
		fflush (stdin);
		eing = 'u';
		main();
	}
strgc = 0;
}

main (argc, argv)

int	argc;
char	argv[];

{
	int   rc = 0;

//	Signalhandler initialisieren - Beim Empfang der spezifizierten
//      Signale wird in die entsprechende Routine gesprungen. 
//	Die Werte der Signale stehen in signal.h 
//	Signal SIGKILL kann nicht abgefangen werden

	signal(SIGINT, sigint);
	signal(SIGHUP, sighup);

        printf("My PID: %d My PPID: %d\n",getpid(), getppid());

//	Warten auf die Signale

	for (;;)
	{
		if (strgc==0){
			printf("Waiting for Signal ..\n");
			sleep(5);}
		else{
			irhandler();}
	}
	exit(rc);
}


//	Wenn das Signal SIGINT (Nr. 2) an diesen Prozess geschickt wird,
//	dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
//	Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
//	sichert den Zustand so dass mit dem return wieder an der Stelle
//	weitergemacht wird an der Prozess war als das Signal empfangen
//	wurde.

void sigint(signo)

int	signo;
{
	//sigrecv++;
	strgc = 1;
	fflush (stdin);
	fflush (stdout);
}

//	Wenn das Signal SIGHUP (Nr. 1) an diesen Prozess geschickt wird,
//	dann wird aus der obigen Endlosschleife zu dem mit dem signal()-
//	Aufruf definierten Unterprogramm gesprungen. Das Betriebssystem
//	sichert den Zustand so dass mit dem return wieder an der Stelle
//	weitergemacht wird an der Prozess war als das Signal empfangen 
//	wurde. 

void sighup(signo)

int	signo;
{
	sigrecv++;
	printf("Signal Nr. %d received by sighup, %d signal\n", signo,sigrecv);
	fflush (stdout);
	exit(1);
}

 

Labor 5:

Fork-Aufrug erstellt Kopie eines Prozesses
Ziel: Erzeugen eines neuen Prozesses
Fork kehrt 2x zurück :-)
	+-------------+        +-------------+
	|             |        |             |
	|      P1     |        |      P2     |
	|             |	fork() |             |
	+-------+-----+        +-------------+
		|		     |		Benutzerebene
----------------+--------------------+-----------------------
		|		     |		Systemebene
	#=============#     #=============#
	"             "     "             "
	"    Heap     "---->"    Heap     "
	"   Daten     "     "   Daten     "
	#=============#     #=============#

Alter und neuer Prozess haben die gleichen Rechte
Rückgabewert = 0 neuer Prozess
Rückgabewert = 1 alter Prozess
 
#include < stdio.h >
#include < errno.h >

main (argc, argv)
int argc;
char *argv[];

{
	int	retval = 0;

	if ( (retval = fork()) < 0)
	{
		printf("\nFehler beim Erzeugen des Prozess: Fehlernummer: %d\n", errno);
		fflush (stdout);
		perror("");
	}
	else if (retval != 0)
	{
		printf("I am the parent. My PID: %d, My parent is %d\n",getpid(), getppid());
		sleep(15);
	}
	else
	{
		printf("I am the child. My PID: %d, My parent is %d\n",getpid(), getppid());
		sleep(15);
	}
	exit(retval);
}

 

Labor 6 aka Abschlussuebung:

Ziel dieser Uebung ist das Erstellen eines etwas umfangreicheren Programms unter Anwendung des bisher gelernten:
Aufgabenstellung und Tips
 
Startprogramm:
#include < stdio.h >
#include < errno.h >
#include < sys/stat.h > /* Braucht man fuer mknod */

main (argc, argv)
int argc;
char *argv[];

{
	int retval = 0;

	//Da kommen die pids der Kinderprozesse rein
	int pidSender, pidEmpfaenger;

	//Named Pipe mit dem Namen myfifo und rw-r--r-- Berechtigungen erstellen
	mknod("myfifo", S_IFIFO | 0644 , 0);
	

	// Erster fork fuer das Sendeprogramm
	retval = fork();
	if ( retval < 0)
	{
		printf("\nFehler beim Erzeugen des Prozess: Fehlernummer: %d\n", errno);
		fflush (stdout);
		perror("");
	}
	else if (retval != 0)
	{
		//Vaterprozess
	}
	else
	{
		execlp( "xterm", "xterm", "-geometry", "+550+10", "-e", "./sendeprogramm", NULL );
		pidSender = getpid();
	}


	// Zweiter fork fuer das Empfangsprogramm
	retval = fork();
	if ( retval < 0)
	{
		printf("\nFehler beim Erzeugen des Prozess: Fehlernummer: %d\n", errno);
		fflush (stdout);
		perror("");
	}
	else if (retval != 0)
	{
		//Vaterprozess
	}
	else
	{
		execlp( "xterm", "xterm", "-geometry", "+550+380", "-e", "./empfangsprogramm", NULL );
		pidEmpfaenger = getpid();
	}

	//Billigloesung, wartet aber garantiert auf beide Soehne
	wait();
	wait();

	//Named pipe loeschen
	execlp( "rm", "rm", "myfifo", NULL );

	exit(retval);
}

 
Sendeprogramm:
#include < stdio.h >
#include < errno.h >

main (argc, argv)
int argc;
char *argv[];

{
	printf("Ich bin das Sendeprogramm\n");

	//Datei Zeiger definieren
	FILE *rohr;

	//fifo Datei zum Schreiben oeffnen
	rohr=fopen("myfifo", "r+");

	char c;
	while((c=getchar()) != '.'){
		fputc(c, rohr);

		//Bei Eingabetaste uebertragen
		if(c = '\n'){
			fflush(rohr);
		}

		
	}

	//Sonst kriegt das Empfangsprogramm ja nie sein '.'
	fputc('.', rohr);
	fflush(rohr);

	printf("\nEnde\n");
	fclose(rohr);
	exit(0);
}

 
Empfangsprogramm:
#include < stdio.h >
#include < errno.h >

main (argc, argv)
int argc;
char *argv[];

{
	printf("Ich bin das Empfangsprogramm\n");

	//Datei Zeiger definieren
	FILE *rohr;

	//fifo Datei zum Schreiben oeffnen
	rohr=fopen("myfifo", "r+");

	char c;
	while((c=fgetc(rohr)) != '.'){
		putchar(c);
	}

	printf("\nEnde\n");
	fclose(rohr);
	exit(0);
}

 
Die Musterloesung, auch ganz nett, aber lange nicht so low-level-hack-maessig :-) wie meine.