Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   Delphi ServerSocket/TcpServer als Webserver -> Schwierigkeiten (https://www.delphipraxis.net/143736-serversocket-tcpserver-als-webserver-schwierigkeiten.html)

Darkface 22. Nov 2009 09:23


ServerSocket/TcpServer als Webserver -> Schwierigkeiten
 
Hiho DP'ler,

Ich habe vor einen kleinen Webserver zu schreiben der einfach auf Anfrage eines Browsers eine sich ständig ändernde Webseite ausgibt.

Ich dachte mir also ich nehme mir die ServerSocket Komponente oder alternativ die TcpServer Komponente und reagiere einfach immer wenn ein Browser eine Anfrage sendet damit drauf das ich dem Browser simpel mit SendText den Quellcode der Seite rüberschiebe.

Das funktioniert aber mit beiden Komponenten nur halb.

Delphi-Quellcode:
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
var i:Integer;
begin
    s:=Socket.ReceiveText;
    Memo1.Lines.Add(s);
    For i:=0 to ServerSocket1.Socket.ActiveConnections -1 do begin
        ServerSocket1.Socket.Connections[i].SendText('<html><head><meta http-equiv="refresh" content="1; URL=/"></head><body>It Works? Or? '
        +IntToStr(Random(10000)) +'</body></html>');
        //ServerSocket1.Active:=False;
        //ServerSocket1.Active:=True;
    end;
end;
Das war der sehr unsaubere Code um auf die Anfrage des Browsers mittels ServerSocket zu reagieren. Wie ihr sehr es soll nicht schön sein sondern einfach nur funktionieren :-D. Problem dabei, der Browser würde unendlich lang die Webseite laden wenn ich nicht mittels ServerSocket1.Active die Verbindung kurz unterbrechen würde. Wenn ich das tue wird die Webseite dann auch Problemlos im Browser angezeigt. Das ist aber selbst mir zu unsauber :) - sobald mehrere Leute versuchen die Seite aufzurufen bekomme ich Probleme, da fliegen mir teils Exceptions um die Ohren und die Browser zeigen immerwieder an das die Verbindung abgebrochen/zurückgesetzt ect. ist.

Beim TcpServer habe ich es ähnlich versucht...
- empfangen der Browser Anfrage
- die in Memofeld schreiben
- *Browser das Stückchen rohes Fleisch hinwerf* (Per SendText den Quellcode rübersenden)

Also imprinzip wie oben. Problem ist hier das der Browser nur bei jedem 5ten Versuch die Webseite anzeigt - dann auch korrekt. Bei den anderen 4 Versuchen kommt ein "Verbindung zurückgesetzt". Beim TcpServer habe ich übrigens nicht das Problem mit dem "unendlichen Laden" der Seite, wenn die Seite angezeigt wird dann richtig.

Wie bekomme ich das ganze also zuverlässiger hin? Wo hängts? Weis da jemand Rat :?:

sirius 22. Nov 2009 09:37

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Ich kenne micht mit http nicht so aus, aber willst du nicht wenigstens einen http-Header mitschicken?

Edit: Und ob die Verbindung geschlossen werden muss, steht doch in der Anfrage drin, oder?

Darkface 22. Nov 2009 09:43

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Wenn dadurch mein Problem nicht hervorgerufen wird dann eigentlich nicht. Ich bekomme ja (zwar unzuverlässig aber es geht) den Quelltext den ich an den Browser zurücksende angezeigt. Nur halt mit oben genannten Problemen. Würde ein Header das lösen?

Ich kenne mich mit dem Protokoll leider auch nicht aus und hatte gehofft das es reicht dem Browser einfach Text zurückzusenden und er sich damit zufrieden gibt. Das wiegesagt klappt ja auch (teilweise) ^^.

Edit: Danke für die schnelle Antwort ^^.

sirius 22. Nov 2009 09:49

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Egel ob http oder ein anderes Anwendungsprotokoll: Ein Header ist insofern wichtig, als dass er mindestens die Größe/Länge des Inhalts angibt. Ohne diese Angabe ist es dem Empfänger unmöglich, das Gesendete korrekt zusammenzusetzen. TCP/IP kann diese Funktionalität nicht liefern. Es zerhackt dir deinen Text in mehrere Pakete und irgendiwe muss der Empfänger ja wissen, wie lange er noch warten muss.
==> Mindestens eine Längenangabe o.ä#. muss in so ein Anwendungsprotokoll. Und http hat das IMHO auch enthalten. Sollte man auch dringend nutzen.
Und evtl. gibt es noch andere wichtige Angaben im Header, keine Ahnung.

Darkface 22. Nov 2009 11:36

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Und wie sieht so ein Http Header in Delphi aus? Send ich das einfach als Text vorne weg oder wie funktioniert das? Hab mir so ein Http Header grad mal angesehen, da kommt so eine Längenangabe in Byte vor, aber wie kann ich die Angeben wo ich doch nur Texte (SendText) und keine Dateien mit festen Dateigrößen in Streams versende?

thkerkmann 22. Nov 2009 11:43

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Hi,

schau mal in den Demos nach, da gibt es einen HTTPserver als Beispiel.

Ausserdem solltest Du die Antwort an den "Socket" = TCustomWinSocket zurückschicken, und diesen schliessen. Das wäre die richtige Vorgehensweise.

Gruss

DataCool 22. Nov 2009 11:51

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Hi Darkface,

nimm doch den IdHttpServer(Indy-Komponenten) der regelt für Dich alles.
Du must nur auf die Events:

- OnCommandGet und
- OnCommandOther

entsprechend die Antwortvariablen füllen, das war.

Greetz Data

sirius 22. Nov 2009 12:32

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Zitat:

Zitat von Darkface
Und wie sieht so ein Http Header in Delphi aus? Send ich das einfach als Text vorne weg ...

Genau.
Delphi-Quellcode:
//mit meinen bescheidenen http-Kentnissen:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, scktcomp, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { Private-Deklarationen }
    FServer:TServersocket;
    FIndex:Integer;
  public
    { Public-Deklarationen }
    procedure ClientConnect(Sender:TObject; Socket: TCustomWinSocket);
    procedure ClientDisconnect(Sender:TObject; Socket: TCustomWinSocket);
    procedure ClientRead(Sender:TObject; Socket: TCustomWinSocket);
    procedure ClientWrite(Sender:TObject; Socket: TCustomWinSocket);
    procedure ClientError(Sender:TObject; Socket: TCustomWinSocket;
                 ErrorEvent: TErrorEvent; var ErrorCode: Integer);
  end;

  TClientData=class
    Constructor Create;
    Destructor Destroy; override;
   private
    FCanWrite: boolean; //ist socket bereit zum senden
    FIndex: Integer; //laufender Index
    FSocket: TCustomWinSocket; //Parent Socket merken, für die Antwort
    FReceivedText:TStringList; //Stringlist wegen zu erwartenden http-Header
    procedure SetCanWrite(const Value: boolean);
    procedure SetIndex(const Value: Integer);
    procedure SetSocket(const Value: TCustomWinSocket);
   protected
    procedure SendNotImpl;
    procedure SendOK(const content:String);
   public
    property CanWrite:boolean read FCanWrite write SetCanWrite;
    property Index:Integer read FIndex write SetIndex;
    property Socket:TCustomWinSocket read FSocket write SetSocket;
    procedure Receive(const Text:string);

  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ClientConnect(Sender: TObject; Socket: TCustomWinSocket);
var ClientData:TClientData;
begin
  ClientData:=TClientData.Create;
  ClientData.CanWrite:=false;
  inc(FIndex);
  ClientData.Index:=FIndex;
  ClientData.Socket:=Socket;
  Socket.Data:=ClientData;
  memo1.lines.add(format('%d connected',[ClientData.Index]));
end;

procedure TForm1.ClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  assert(TObject(Socket.Data) is TClientData);
  memo1.lines.add(format('%d disconnected',[TClientData(Socket.Data).Index]));
  TObject(Socket.Data).Free;
end;

procedure TForm1.ClientError(Sender: TObject; Socket: TCustomWinSocket;
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);
var s:String;
begin
  assert(TObject(Socket.Data) is TClientData);
  case ErrorEvent of
    eeGeneral: s:='General';
    eeSend: s:='Send';
    eeReceive: s:='Receive';
    eeConnect: s:='Connect';
    eeDisconnect: s:='Disconnect';
    eeAccept: s:='Accept';
    eeLookup: s:='Lookup';
    else s:='Empty';
  end;
  memo1.Lines.Add(format('%d: Error on %s',[TClientData(Socket.Data).index,s]));
  ErrorCode:=0;
end;

procedure TForm1.ClientRead(Sender: TObject; Socket: TCustomWinSocket);
var s:string;
begin
  assert(TObject(Socket.Data) is TClientData);
  s:=Socket.ReceiveText;
  memo1.lines.add(format('%d Read: %s',[TClientData(Socket.Data).index,s]));
  TClientData(Socket.Data).Receive(s);
end;

procedure TForm1.ClientWrite(Sender: TObject; Socket: TCustomWinSocket);
begin
  assert(TObject(Socket.Data) is TClientData);
  memo1.lines.add(format('%d: CanWrite',[TClientData(Socket.Data).index]));
  TClientData(Socket.Data).CanWrite:=true;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FServer:=TServersocket.Create(self);
  FServer.Port:=80;
  FServer.Service:='';
  FServer.ServerType:=stNonBlocking;
  FServer.OnClientConnect:=ClientConnect;
  FServer.OnClientDisconnect:=ClientDisconnect;
  FServer.OnClientWrite:=ClientWrite;
  FServer.OnClientRead:=ClientRead;
  FServer.OnClientError:=ClientError;
  FServer.Open;
  FIndex:=0;
end;


{ TClientData }

constructor TClientData.Create;
begin
  FReceivedText:=TstringList.Create;
end;

destructor TClientData.Destroy;
begin
  FReceivedText.Free;
end;

//neu erhaltenen TExt speichern und überprüfen ob breits eine Leerzeile vorhanden ist
//Leerzeile == Ende HTTP-Anfrage
procedure TClientData.Receive(const Text: string);
var i:Integer;
    Endpos:Integer;
    s:String;
    Req:String;
begin
  FReceivedTExt.Text:=FReceivedTExt.Text+Text;
  while FReceivedTExt.count>0 do
  begin
    EndPos:=-1;
    for i:=0 to FReceivedTExt.count-1 do
    begin
      if FReceivedText.Strings[i]='' then //auf http-Header Ende warten/suchen
      begin
        EndPos:=i; //Leerzeile gefunden
        break;
      end;
    end;

    if Endpos>0 then //wenn leerzeile und noch etwas mehr gefunden
    begin
      //eigentlich hier noch auf CanWrite überprüfen, ansonsten iust unser socket noch nicht fertig
      s:=FReceivedText.Strings[0];
      if Copy(s,1,4)='GET ' then //erste Zeile auseinanderbauen
      begin
        Delete(s,1,4);
        i:=pos(' ',s);

        Req:=copy(s,1,i-1);
        Delete(s,1,i);

        if copy(s,1,5)='HTTP/' then
        begin
          //antworten (eigentlich müsste man noch den Rest des Headers analysieren)
          SendOK(req);
        end else
          SendNotImpl;
      end else
        SendNotImpl;
    end;

    for i:=0 to Endpos do FReceivedText.Delete(0); //alles Bearbeitete löschen
  end;
end;

//"Fehler" senden
procedure TClientData.SendNotImpl;
begin
  FSocket.SendText('HTTP/1.1 501 Not Implemented'#13#10#13#10);
end;

//Antwort senden
procedure TClientData.SendOK(const Content:String);
var Ans:TStringList;
    HTML:String;
begin
  ans:=TStringList.Create;
  try
    HTML:='<html><head><meta http-equiv="refresh" content="1; URL=/"></head><body>It Works? Or? '
        +inttostr(FIndex) +'
'+Content+'</body></html>'#13#10;

    ans.Add('HTTP/1.1 200 OK');
    ans.add('Server: myDelphi');
    ans.add('Content-Length: '+inttostr(length(HTML)));
    ans.add('Content-Language: de');
    ans.add('Content-Type: text/html');
    ans.add('Connection: close');
    FSocket.SendText(Ans.Text+#13#10+HTML);

  finally
    ans.free;
  end;
end;

procedure TClientData.SetCanWrite(const Value: boolean);
begin
  FCanWrite := Value;
end;

procedure TClientData.SetIndex(const Value: Integer);
begin
  FIndex := Value;
end;

procedure TClientData.SetSocket(const Value: TCustomWinSocket);
begin
  FSocket := Value;
end;

end.
Edit: @Datacool: Ich vermute der TE möchte das Prinzip hinter einem http-Server verstehen. Da hilft keine fertige Komponente.

Darkface 22. Nov 2009 12:33

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Zitat:

Zitat von thkerkmann
[...]
in den Demos nach, da gibt es einen HTTPserver als Beispiel
[...]
die Antwort an den "Socket" = TCustomWinSocket zurückschicken, und diesen schliessen
[...]

Habe die Demos gerade durchsucht dort aber nur ein Indy Server Beispiel gefunden. Und Indy hab ich mit meinem Turbo Delphi noch nie zum laufen gebracht was dann auch den Vorschlag von DataCool zunichte macht. Eigentlich auch unnötig wenn ich diese kleine unstabilität aus dem Tcp/Server Socket rausbekommen könnte. Wie meinst du das mit der Antwort an den Socket zurückschicken um ihn zu schließen?

Danke für eure schnelle Hilfe...
Darkface

EDIT: Ach sirius danke für deinen Code ich habs grad erst gesehen, ich werde mich zeitnah damit beschäftigen! :-D

Darkface 23. Nov 2009 16:52

Re: ServerSocket/TcpServer als Webserver -> Schwierigkeit
 
Total genial sirius, vielen Dank für deine Hilfe!
Hab mit deinem Code den mini Webserver umgebaut und es hat wunderbar hingehauen!
:-D

MfG
Darkface


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:15 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz