![]() |
|||
HSG |
|
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.
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.
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
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;
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.
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
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.
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;
timeclient.zip, Timeclient0exe.zip