kleiner Webserver in C

huebstAr

Grünschnabel
Hi an Alle!

Wie im Topic zu lesen soll dieses Programm einen kleinen Webserver darstellen, der zunächst einmal nur die Seite bei einem Request in den Socket zurückschreiben soll.

Leider scheint hier irgendwo ein Fehler zu sein, da ich, wenn ich die Seite 127.0.0.1 aufrufe, eine Fehlermeldung im Browser bekomme.

Ich stehe nur etwas auf dem Schlauch, da die Kontrollausgaben im Programm so aussehen, wie ich sie mir vorgestellt habe.

Wisst ihr vielleicht weiter?

Code:
//
// Programm:         einfacher Webserver
// Funktiosumfang:    Lauschen auf Port 80, bei Anfrage einer Seite,
//            diese zum anfragenden Client zurücksenden


//----------------------------------------------------------------------------
// Einbinden der Headerdateien
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

// Prototypen der verwendeten Funktionen
int deploy_server();
void process_request(int result_accept);

int main()
{
    int     sock=-1,
        source=0,
        result_accept=0,
        pid=-1,
        len_addr=0,
        status=0;
    struct sockaddr_in requestor;
   
    printf(".\n");
    printf(".\n");   
    printf("Webserver v1.0 gestartet\n");
    printf(".\n");
    printf(".\n");

    do {

        sock = deploy_server();
        if(sock<0)
        {       
            printf("Fehler beim Deployen des Servers!\n");
            printf("<RETURN> für Neuversuch\n\n");
            getchar();
        }

    }while(sock < 0);

    while(1)
    {
        len_addr = sizeof(source);
        result_accept = accept(sock, (struct sockaddr*) &source, &len_addr);
       
        if(-1 == result_accept)
        {
            printf("Fehler beim Verbindungsaufbau\n");
        }
        else
        {
            printf("Verbindungsaufbau erfolgreich\n");
        }

        pid = fork();
       
        if(-1 == pid)
        {   
            printf("Fehler beim erstellen des Sohnprozesses!\n");
        }
       
        if(0 == pid)
        {
            process_request(result_accept);
            exit(0);
            waitpid(-1, &status, WNOHANG);
            printf("Status: %d", status);
        }
       
        close(result_accept);

    }

}

int deploy_server()       
{
    int     sock,
        result_bind=5;
    struct     sockaddr_in adressinfo;

    sock = socket(AF_INET, SOCK_STREAM, 0);
   
    bzero(&adressinfo, sizeof(adressinfo));
   
    adressinfo.sin_family = AF_INET;
    adressinfo.sin_port = htons(80);
    adressinfo.sin_addr.s_addr = INADDR_ANY;
   
    result_bind = bind(sock, (struct sockaddr*) &adressinfo, sizeof(adressinfo));

    if(result_bind == -1)
    {   
        printf("Fehler bei Bind");
        return -1;   
    }

    listen(sock, 2);
    return(sock);
}

void process_request(int sock)
{
    char     	sendbuffer[8096],
        	request[8096],
		filename[8096],
        	buffer;
    int    	i=0,
        	k=0,f=0,
        	ext_laenge=0,
        	anzahl=0,
        	file_descript=0;
    char     	get_ext[4];
   
    	bzero(&request, sizeof(request));
	bzero(&filename, sizeof(filename));
   
    while(i < sizeof(request)-1)    // solange die Länge des Strings nicht überschritten wird
    {
        if(    read(sock, &buffer, 1) > 0    // Solange ein Zeichen gelesen werden kann
            && buffer != '\n'         // UND kein Zeilenumbruch
            && buffer != '\r')
        {       
            request[i] = buffer;        // weiter einlesen
            i++;
        }
        else
        {
            request[i] = '\0';        // Sonst String abschließen
            break;                // und Schleife abbrechen
        }
    }

    // Kontrollausgabe der Anfrage
    for(k=0; k< sizeof(request); k++)
        printf("%c", request[k]);
    printf("\n\n");
   
    // Prüfen ob Get-Anfrage (nicht Case-Sensitive)
    if(!strncmp(request, "GET ", 4) || !strncmp(request, "get ", 4)) // falls GET
    {   
        i=5;        					// Startwert des Index, GET wird ignoriert
   
        if(!strncmp(&request[5], " ", 1))    		// Wenn keine Seite angegeben
        {     
            	strcpy(request, "GET /index.html");    	// String auf index.html setzen                              
        }
       
	while(strncmp(&request[i], " ", 1) != 0)	// SOLANGE KEIN Leerzeichen gefunden
	{      
		strncpy(&filename[f], &request[i], 1);	// Zeichen an Filename anhängen
		f++; i++;
	}

        // Extensions prüfen
        // FEHLT NOCH
        // FEHLT NOCH
        // FEHLT NOCH
        // FEHLT NOCH
   
	printf("OPENING: >%s<\n", filename);
	file_descript=open(filename, O_RDONLY);		// Angefordertes File öffnen
        
	if(file_descript != -1)
        {
         /*   	while(anzahl=read(file_descript, sendbuffer, sizeof(sendbuffer)))
                	write(sock, sendbuffer, anzahl);*/
		write(sock, sendbuffer, read(file_descript, sendbuffer, sizeof(sendbuffer)));
	        close(file_descript);
        }       
        else
            printf("Fehler! File konnte nicht geöffnet werden!\n");
   
        close(sock);   
	printf("--> ENDE \n");
    }
    else
        printf("Nur GET-Anfragen werden bearbeitet!\n");
}

Desweiteren bin ich mir nicht ganz sicher, ob die Anweisung close(sock); (*in Zeile 185) hier an der richtigen Stelle steht, da dort der socket ja eignetlich immer offen gehalten werden sollte?!

Wo kann ich sie sonst unterbringen? Das Programm wird auf einem SuSe-System ausgefürt und mit Strg+C terminiert. Ich habe die Vermutung, dass hier eventuell der Fehler steckt, da nach einiger Zeit der Aufruf zunächst funktioniert (Seite wird an Client zurückgegeben) und anschließend für eine längere Zeit nicht mehr.


Gruß
huebstAr
 
Hi,

welche Fehlermeldung? Hast du schon mal den Request über telnet abgesetzt und geschaut was wirklich vom Server kommt?

Welchen Standard folgst du? HTTP/1.0 oder HTTP/1.1?

Gruß
BK
 
Hi!

Das mit Telnet werde ich gleich mal versuchen.

Hier zu der Fehlermeldung:
snapshot1.png

Gruss

Kleiner Edit: Das Spuckt telnet aus
snapshot3.png

Wobei "lalelu.txt" der Inhalt von index.html ist.
 
Zuletzt bearbeitet:
Hi

Dein Server schließt nach Übertragung die Socketverbindung von sich aus.
Diesen Teil sollte der Browser übernehmen.
Sonst kann es passieren, dass nicht alle Byte ankommen.

Also nach Übertragung weitere Befehle abwarten.
Wenn recv mit einem Fehlerreturnwert kommt (macht es bei geschlossener Verbindung),
dann Ende.

Gruß
 
Hi!

Also verstehe ich nun richtig: Das close(socket); sollte nicht an dieser Stelle stehen, sondern wenn von dem Browser (Client) eine Fehlermeldung kommt, aufgerufen werden?

€: Nein, falsch verstanden, ich Denke du meinst das close(result_accept); !?
 
Zuletzt bearbeitet:
Beides nur so halb.
Erstens handelt es sich bei result_accept und sock in process_request
um den gleichen Socket.

Dann schließt du (nicht in jedem Fall, aber doch) sock schon in process_request,
und dann schließt du result_accept nocheinmal.

Drittens soll das nicht gemacht werden, wenn die Fehlermeldung kommt,
sondern eben, um diese zu verhindern.


Der Browser schickt "GET irgendwas".
Dein Server schickt den Inhalt (wo ist eigentlich der Header :suspekt:).
Wenn der Browser alles hat, schließt der die Verbindung.
Der Server muss warten, ob geschlossen wird oder weitere Befehle kommen.

Abe wo der Head ist, wäre interessant...
 
Danke erstmal!

Beides nur so halb.
Erstens handelt es sich bei result_accept und sock in process_request
um den gleichen Socket.

Dann schließt du (nicht in jedem Fall, aber doch) sock schon in process_request,
und dann schließt du result_accept nocheinmal.
Leuchtet mir so halb ein. Wenn ich den Socket schon geschlossen habe und dann nochmals schließe (wobei aber noch keine neue Socket-Nr eingetragen wurde), passiert ja im Prinzip nichts, oder?

Drittens soll das nicht gemacht werden, wenn die Fehlermeldung kommt,
sondern eben, um diese zu verhindern.
Hier weiß ich nicht, was du damit meinst. Der Socket soll doch nach jedem mal geschlossen werden, falls eine Seite übertragen wurde. Sofern bin ich mir nun nicht ganz sicher, was du meinst mit "Fehlermeldung verhindern".

Dein Server schickt den Inhalt (wo ist eigentlich der Header :suspekt:).
Wenn der Browser alles hat, schließt der die Verbindung.
Meinst du den Kopf der HTML-Seite? Habe ich nun nachträglich eingefügt. Das dürfte ja aber nicht zu der nicht-anzeige führen, oder?

Der Server muss warten, ob geschlossen wird oder weitere Befehle kommen.
Wie kann ich auf dieses Ereignis warten? Was schickt der Browser denn für eine Stop-Condition? Der Server soll nur eine Seite anzeigen, weiteres nicht.


Gruß
 
Hi,

zuerstmal: Es ist egal, ob der Server oder der Client die Verbindung schließt.
Bei persistenten Verbindungen (ab HTTP/1.1) wird die Verbindung (mit einem richtigen Header) zum Beispiel primär vom Client geschlossen, wenn der nichts mehr zu sagen hat. Bei HTTP/1.0 oder nicht persistenten Verbindungen schließt der Server die Verbindung, sobald er alle Daten gesendet hat. Der entsprechende Header nennt sich "Connection" und hat dann entweder "close" oder "Keep-Alive" als Wert.

Ich denke mal, dass du das HTTP-Protokoll nicht RFC-konform umsetzt und sich daher der Firefox weigert was anzuzeigen.

Wie sieht dein Request an den Server und die Antwort darauf exakt aus?
Es sollte in etwa so aussehen:
Code:
/* REQUEST START */
GET /index.html HTTP/1.0
Zusaetzliche-Header: Felder

/* REQUEST ENDE */
/* ANSWER START */
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 29

dies ist der text der antwort
/* ANSWER ENDE*/

Woruaf ich dich besonders hinweisen möchte: Zwischen dem Header und dem Body steht eine Leerzeile (genauso am Ende des Requests) und bei der Antwort muss das Feld "Content-Length" gesetzt sein. Ob der "Content-Type" auch benötigt wird, kann ich dir ausm Gedächtnis leider nicht sagen, würde den aber trotzdem mitsenden.

Edit: Hier noch ein bisschen was zum Lesen für dich: RFC 2616

Gruß,
BK
 
Zuletzt bearbeitet:
Hi!

Danke erstmal für die Ausführung. Habe nun (erst einmal) den Header so eingefügt. Da ich noch einige kleiner Änderungen durchgeführt habe, schreibe ich hier nochmal den aktuellen Code rein:

Code:
//----------------------------------------------------------------------------
// Einbinden der Headerdateien
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

// Prototypen der verwendeten Funktionen
int deploy_server();
void process_request(int result_accept);

int main()
{
    int     sock=-1,
        source=0,
        result_accept=0,
        pid=-1,
        len_addr=0,
        status=0;
    struct sockaddr_in requestor;
   
    printf(".\n");
    printf(".\n");   
    printf("Webserver v1.0 gestartet\n");
    printf(".\n");
    printf(".\n");

    do {

        sock = deploy_server();
        if(sock<0)
        {       
            printf("Fehler beim Deployen des Servers!\n");
            printf("<RETURN> für Neuversuch\n\n");
            getchar();
        }

    }while(sock < 0);

    while(1)
    {
        len_addr = sizeof(source);
        result_accept = accept(sock, (struct sockaddr*) &source, &len_addr);
       
        if(-1 == result_accept)
        {
            printf("Fehler beim Verbindungsaufbau\n");
        }
        else
        {
            printf("Verbindungsaufbau erfolgreich\n");
        }

        pid = fork();
       
        if(-1 == pid)
        {   
            printf("Fehler beim erstellen des Sohnprozesses!\n");
        }
       
        if(0 == pid)
        {
            process_request(result_accept);
            exit(0);
            waitpid(-1, &status, WNOHANG);
            printf("Status: %d", status);
        }
       
        close(result_accept);

    }

}

int deploy_server()       
{
    int     sock,
        result_bind=5;
    struct     sockaddr_in adressinfo;

    sock = socket(AF_INET, SOCK_STREAM, 0);
   
    bzero(&adressinfo, sizeof(adressinfo));
   
    adressinfo.sin_family = AF_INET;
    adressinfo.sin_port = htons(80);
    adressinfo.sin_addr.s_addr = INADDR_ANY;
   
    result_bind = bind(sock, (struct sockaddr*) &adressinfo, sizeof(adressinfo));

    if(result_bind == -1)
    {   
        printf("Fehler bei Bind");
        return -1;   
    }

    listen(sock, 5);
    return(sock);
}

void process_request(int sock)
{
    char     	sendbuffer[8096],
        	request[8096],
		filename[8096],
        	buffer;
    int    	i=0,
        	k=0,f=0,
        	ext_laenge=0,
        	anzahl=0,
        	file_descript=0;
    char     	ex_extension[4];
   
    	bzero(&request, sizeof(request));
	bzero(&filename, sizeof(filename));
	bzero(&sendbuffer, sizeof(sendbuffer));
	bzero(&ex_extension, sizeof(ex_extension));
   
    while(i < sizeof(request)-1)    // solange die Länge des Strings nicht überschritten wird
    {
        if(    read(sock, &buffer, 1) > 0    	// Solange ein Zeichen gelesen werden kann
            && buffer != '\n'         		// UND kein Zeilenumbruch
            && buffer != '\r')
        {       
            request[i] = buffer;        // weiter einlesen
            i++;
        }
        else
        {
            request[i] = '\0';        	// Sonst String abschließen
            break;                	// und Schleife abbrechen
        }
    }

    // Kontrollausgabe der Anfrage
    for(k=0; k< sizeof(request); k++)
        printf("%c", request[k]);
    printf("\n\n");
   
    // Prüfen ob Get-Anfrage (nicht Case-Sensitive)
    if(!strncmp(request, "GET ", 4) || !strncmp(request, "get ", 4)) // falls GET
    {   
        i=5;        					// Startwert des Index, GET wird ignoriert
	f=0;
   
        if(!strncmp(&request[5], "\0", 1) || !strncmp(&request[5], "\0", 1))    // Wenn keine Seite angegeben
        {     
		printf("keine page\n");
            	strcpy(request, "get /index.html ");    	// String auf index.html setzen                              
        }
       
	while(strncmp(&request[i], " ", 1))	// SOLANGE KEIN Leerzeichen erreicht
	{      
		strncpy(&filename[f], &request[i], 1);	// Zeichen an Filename anhängen
		f++; i++;
	}

	i=0;
	
        // Extensions extrahieren
        while(strncmp(&filename[i], ".", 1))    	// Punkt suchen
        {     
		i++;
        }
	strcpy(ex_extension, &filename[i+1]);		// Fileextension extrahieren
   
	 // Extension zuordnen
	 // FEHLT
	 // FEHLT
	 // FEHLT
	  
	printf("OPENING: >%s<\n", filename);
	printf("EXTENSION: >%s<\n", ex_extension);
	file_descript=open(filename, O_RDONLY);		// Angefordertes File öffnen
        
	if(file_descript != -1)
        {
	    sprintf(request, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n");
	    anzahl=read(file_descript, sendbuffer, sizeof(sendbuffer));
	    {
		write(sock, sendbuffer, anzahl);
	    }
	    
	    close(file_descript);
        }       
        else
            printf("Fehler! File konnte nicht geöffnet werden!\n");
   
	printf("--> ENDE \n");
    }
    else
        printf("Nur GET-Anfragen werden bearbeitet!\n");
}

Nun, das Programm tut bis hierhin was es soll, jedenfalls via Telnet. Das Problem ist, dass der FF scheinbar HTTPTML/1.1 requestet.
Kann man den Firefox irgendwie 'toleranter' schalten?

Das interessante ist, dass durchschnittlich jedes 2. Mal die Page angezeigt wird, wenn ich einfach nochmal einen Refresh durchfuehre, kann es sein, dass die Page mit der Fehlermeldung "The connection was reset" terminiert wird.

Im Fenster des Programms "webserver" auf Konsolenebene wird jedoch der richtige Request / bzw. nichts auffaelliges angezeigt.
Code:
OPENING: >index.html<
EXTENSION: >html<
--> ENDE
Verbindungsaufbau erfolgreich
GET /index.html HTTP/1.1

OPENING: >index.html<
EXTENSION: >html<
--> ENDE
Verbindungsaufbau erfolgreich
GET /index.html HTTP/1.1

OPENING: >index.html<
EXTENSION: >html<
--> ENDE
Verbindungsaufbau erfolgreich
GET /index.html HTTP/1.1

OPENING: >index.html<
EXTENSION: >html<
--> ENDE
Verbindungsaufbau erfolgreich
GET /index.html HTTP/1.1

OPENING: >index.html<
EXTENSION: >html<
--> ENDE

Hat da jemand ne Idee?
 
Zuletzt bearbeitet:
Zurück