HSG

Aktuelle Seite: HSG/Fächer/Informatik/Netze

TWSocket

Klassendiagramm zu TWSocket In den folgenden Beispielen wird die - freie - Klasse TWSocket von Francois Piette verwendet, die wiederum Windows-Api-Funktionen kapselt. Nebenstehendes Klassendiagramm zeigt nur den Teil der Attribute und Methoden, die für einen ersten Einstieg gebraucht werden.
Zu der Klasse gibt es eine Windows-Hilfe-Datei (WSocketHlp.zip), die die meisten Attribute und Methoden erläutert.
In den Beispiel-Programmen wird TWSocket dynamisch verwendet, dh. es ist keine Installation der Komponenten erforderlich. Für erste Versuche werden mindestens die Dateien WSocket.pas, WSockBuf.pas und icsdefs.inc benötigt, die man in miniICS.zip bzw.miniICS06.zip (ca. 48kB) findet.


Verwendung von TWSocket am Beispielprogramm 'Client0'

Zunächst wird man die Dateien aus miniICS in das Projekt integrieren. Die dpr-Datei sieht dann etwa so aus:

program Client0;

{%File '..\miniICS\icsdefs.inc'}

uses
  Forms,
  uClient0 in 'uClient0.pas' {ClientForm},
  WSockBuf in '..\miniICS\WSockBuf.pas',
  WSocket in '..\miniICS\WSocket.pas';

{$R *.RES}

begin
  Application.CreateForm(TClientForm, ClientForm);
  Application.Run;
end.

Dann kann man die Benutzungsoberfläche (GUI) 'zusammenschieben'. Im Beispiel sind die Felder für eingehende (mEin)und ausgehenden (mAus) Daten sowie das Meldungsfeld (mMeldungen) vom Typ TMemo.

GUI zu client0

Die Einbindung eines Objekts socket vom Typ TWSocket erfolgt in der üblichen Weise. Der Konstruktor von TWSocket erwartet als Parameter eine Komponente als Eigentümer. Trägt man hier nil ein, so ist der Konstruktor zufrieden. Allerdings muss man dann selbst Erzeugung, Freigabe und Ereignis -Verknüpfung regeln.

procedure TClientForm.FormCreate(Sender: TObject);
begin
  // Erzeugung
  socket := TWSocket.Create(nil);                         // owner = nil
  // Verbinden der Ereignis-Behandlungen
  socket.OnSessionConnected := socketSessionConnected;    // hier keine Parameter angeben
  socket.OnSessionClosed    := socketSessionClosed;
  socket.OnDataAvailable    := socketDataAvailable;
end;

procedure TClientForm.FormDestroy(Sender: TObject);
begin
  socket.Free;
end;

Beim Schreiben der Ereignisbehandlungsroutinen müssen die Parameter wie von ICS vorgesehen verwendet werden.

// Ereignis-Behandlung
procedure TClientForm.SocketSessionConnected(Sender: TObject; Error: Word);
begin
  FError := Error;      // Fehlercode intern in FError merken
  if Error <> 0
  then
    mMeldungen.Lines.Add('keine Verbindung, Fehlernummer: ' + IntToStr(Error))
  else
    mMeldungen.Lines.Add('Verbindung steht! Port = '+Socket.GetXPort);
  // nur sinnvolle Buttons aktivieren
  bDisconnect.Enabled := true;
  bConnect.Enabled    := false;
end;

// Ereignis-Behandlung
procedure TClientForm.SocketDataAvailable(Sender: TObject; Error: Word);
begin
  // der empfangene String wird angezeigt
  mEin.Lines.Add(Socket.ReceiveStr);
end;

// Ereignis-Behandlung
procedure TClientForm.SocketSessionClosed(Sender: TObject; Error: Word);
begin
  if FError = 0
  then
    if Error <> 0
    then
      mMeldungen.Lines.Add('Verbindung beendet, Fehlernummer: ' + IntToStr(Error))
    else
      mMeldungen.Lines.Add('Verbindung beendet!');
  // nur sinnvolle Buttons aktivieren
  bDisconnect.Enabled := false;
  bConnect.Enabled    := true;
end;

Bevor eine Verbindung aufgebaut wird, übergibt man Adresse, Protokoll und Portnummer als Strings. Anschließend erhält das Object den Auftrag zur Verbindung. Eine Verbindung kann nur zustande kommen, wenn an der angegebenen Adresse ein Serverprogramm mit der richtigen Portnummer läuft, das die Verbindung annimmt. Der Client braucht einen Server.

procedure TClientForm.bConnectClick(Sender: TObject);
begin
  Socket.Addr   := eAdresse.Text;
  Socket.Proto  := 'tcp';
  Socket.Port   := ePort.Text;
  Socket.Connect;              // versuche, Verbindung aufzubauen
  // nach dem effektiven Aufbau wird OnSessionConnected ausgelöst
end;

Zum Abbau der Verbindung sendet man dem Objekt die entsprechende Nachricht.

procedure TClientForm.bDisconnectClick(Sender: TObject);
begin
  Socket.Close;
  // beim effektiven Schluss wird OnSessionClosed ausgelöst
end;

Um Daten zu senden, genügt es, den zu sendenden String in den Ausgabepuffer zu übertragen. Das Objekt kümmert sich selbstständig um den Versand.

procedure TClientForm.bSendenClick(Sender: TObject);
begin
  // zu sendender String wird in den Ausgabepuffer übertragen
  Socket.Text := mAus.Text;
end;

Zu Testzwecken kann man sich mit irgendeinem Server verbinden. Auf obigem ScreenShot wurde zu dem Server 131.246.120.81 auf Port 80 (http) eine Verbindung aufgebaut und die Anforderung
GET /abc.html HTTP/1.1
Host: www.hsg-kl.de


gesendet (Achtung: 2 CRLF am Ende!). Der Server hat mit der Lieferung der Datei /abc.html von dem virtuellen Host www.hsg-kl.de reagiert.
Wir haben also einen miniBrowser programmiert. Man sieht übrigens, dass die eigene Portnummer (hier: 3575) zufällig gewählt wurde. Download: client0.zip,Client0exe.zip

Umbau von Client0 zu Server0

Es ist verhältnismäßig leicht, das oben beschriebene Programm zu einem einfachen Server umzubauen. Dazu wird man zuerst den verbinde-Button samt zugehöriger Ereignisbehandlung löschen. Ein Server versucht sich nicht irgendwo hin zu verbinden, sondern er wartet auf eine Verbindungsanforderung. Man braucht also einen Button bListen samt Ereignisbehandlung, der dieses Warten auslöst. Protokoll und Port haben den gleichen Sinn wie bei der Benutzung als Client. Die dem Objekt übergebene Adresse dagegen wählt den zu akzeptierenden Kommunikationspartner aus. Setzt man hier 0.0.0.0 ein, so wird jeder akzeptiert.

procedure TClientForm.bListenClick(Sender: TObject);
begin
  Socket.Addr   := eAdresse.Text;
  Socket.Proto  := 'tcp';
  Socket.Port   := ePort.Text;
  Socket.Listen;              // versuche, Verbindung aufzubauen
  // nach dem effektiven Aufbau wird OnSessionConnected ausgelöst
end;

Versucht sich ein Client anzumelden, so wird das Ereignis OnSessionAvailable ausgelöst. Man kann dann in folgender Weise reagieren: Es wird die Methode Accept aufgerufen, die die Verbindung akzeptiert und ein SocketHandle (eine interne Nummer) liefert. Dieses Handle wird mit der Methode Dup (duplicate) zur weiteren Kommunikation benutzt. Der Socket lauscht jetzt nicht mehr, sondern kommuniziert.

procedure TClientForm.FormCreate(Sender: TObject);
begin
  // Erzeugung
  socket := TWSocket.Create(nil);
  // Verbinden der Ereignis-Behandlungen
  socket.OnSessionConnected := socketSessionConnected;
  socket.OnSessionClosed    := socketSessionClosed;
  socket.OnDataAvailable    := socketDataAvailable;
  socket.OnSessionAvailable := socketSessionAvailable;
end;

.......

// Ereignis-Behandlung
procedure TClientForm.SocketSessionAvailable(Sender: TObject; Error: Word);
var
  SocketHandle : TSocket;
begin
  FError := Error;      // Fehlercode merken
  if Error <> 0
  then
    mMeldungen.Lines.Add('keine Verbindung, Fehlernummer: ' + IntToStr(Error))
  else
    mMeldungen.Lines.Add('Verbindung steht!');
  SocketHandle := Socket.Accept;
  Socket.Dup(SocketHandle);
  Socket.SendStr('Hallo Client!');
  bDisconnect.Enabled := true;
end;

GUI zu server0server0.zip, Server0exe.zip

Server0 kann gut mit Client0 getestet werden. Gibt man als Adresse localhost an, so geht das auf dem selben Rechner. Mehr Spaß macht es natürlich, die Programme im lokalen Netz oder im Internet zu testen. Dazu braucht man die Adresse des Server-Rechners. Auf Windows-Rechnern erfährt man sie zum Beispiel mit ipconfig /all auf der Kommandoebene.

Erweiterung zum Multi-Client-Server

Server0 funktioniert, hat aber einen gravierenden Nachteil: Er nimmt nur eine Verbindung an. Für viele Anwendungen, wie z.B. für eine Chat-Server wird es aber notwendig werden, mehrere Verbindungen anzunehmen und zu behandeln. An einem kleinen Chat-Server sollen mögliche Vorgehensweisen gezeigt werden

GUI zu Server1server1.zip

Der grundsätzliche Unterschied zu server0 besteht darin, dass das Socket empf nur für die Entgegennahme von Verbindungen zuständig ist (Empfangschef) und die Handles den Verbindungssockets verb[i] weitergibt.

Multi-Client-Server

procedure TClientForm.FormCreate(Sender: TObject);
begin
  // Erzeugung, empf nimmt Verbindungen entgegen
  empf := TWSocket.Create(nil);
  empf.Addr   := eAdresse.Text;
  empf.Proto  := 'tcp';
  empf.Port   := ePort.Text;
  empf.Listen;              // versuche, Verbindung aufzubauen
  // nach dem effektiven Aufbau wird OnSessionConnected ausgelöst
  // Verbinden der Ereignis-Behandlungen
  empf.OnSessionAvailable := SessionAvailable;
  empf.OnSessionClosed := SessionClosed;
end;

....

// Ereignis-Behandlung
procedure TClientForm.SessionAvailable(Sender: TObject; Error: Word);
var
  SocketHandle : TSocket;
  n            : byte;
begin
  if Error  0
  then
    mMeldungen.Lines.Add('keine Verbindung, Fehlernummer: ' + IntToStr(Error))
  else
    mMeldungen.Lines.Add('Verbindung steht! Port: '+empf.GetXPort);
  SocketHandle := empf.Accept;
  n := Length(verb); SetLength(verb,n+1);    // Array erweitern
  verb[n] := TWSocket.Create(nil);           // neues Socket
  verb[n].Dup(SocketHandle);                 // Verbindung übernehmen
  verb[n].SendStr('Hallo Client'+IntToStr(n)+'!');
  verb[n].OnDataAvailable := DataAvailable;  // Ereignisbehandlung zuordnen
end;

// Ereignis-Behandlung
procedure TClientForm.DataAvailable(Sender: TObject; Error: Word);
var
  n,i,akt : byte;   // akt = Nummer der aktuellen Verbindung
  hs      : string;
begin
  n := Length(verb);
  for i := 0 to n-1 do
    if Sender = verb[i] then akt := i;
  hs := IntToStr(akt)+'>'+verb[akt].ReceiveStr;
  mEin.Lines.Add(hs);
  for i := 0 to n-1 do verb[i].SendStr(hs);
end;

Weitere Beispiele

TimeClient

GUI zu timeClienttimeclient.zip, Timeclient0exe.zip

DNS-Lookup

GUI zu DNS-Lookupdnslookup.zip

Links

Valid XHTML 1.0!