Zur Ansteuerung der TCP/IP Protokollsoftware bietet Microsoft Windows dem Programmierer das Winsock Interface mit unzähligen Funktionen an. Eine umfangreiche Beschreibung aller Funktionen kann bei [6] gefunden werden. Da die wichtigsten dieser Funktionen aber komplizierte Strukturen als Übergabeparameter benötigen, ist es sinnvoll, wenn man zuerst einige Prozeduren zur einfacheren Ansteuerung erstellt. Zu diesem Zweck habe ich, bevor ich mit der Programmierung des Beispielprogramms angefangen habe, zuerst die Unit WS (für Winsock) geschrieben. Sie bietet einen wesentlich einfacheren Zugriff auf die wichtigsten Funktionen des Winsock APIs.
{*******************************************************}
{ }
{ Ws: Unit zur Ansteuerung des Winsock Interface }
{ Facharbeit: \"TCP/IP: Kommunikation im Internet\" }
{ }
{ Copyright (c) 1999-2000 by Martin Hüsser }
{ }
{*******************************************************}
unit Ws;
interface
uses Winsock;
{Winsock Funktionen}
function Winsock_Init : Boolean;
function Winsock_Terminate : Boolean;
{Socket Funktionen}
function Socket_Open(SockNum:Word) : Boolean;
function Socket_Close(SockNum:Word) : Boolean;
function Socket_Connect(SockNum:Word;Port:Word;Addr:String) : Boolean;
function Socket_Send(SockNum:Word; SendBuf:String) : Boolean;
function Socket_Receive(SockNum:Word; var RecBuf:String) : Boolean;
{Socket Funktionen für Server}
function Socket_Bind(SockNum:Byte;Port:Word) : Boolean;
function Socket_Listen(SockNum:Word;MaxNumOfConn:Integer) : Boolean;
function Socket_Accept(SockNum,SockNum2:Byte) : Boolean;
{Zusätzliche Funktionen}
function Socket_Async(SockNum:Word;WndHandle:Integer;MsgNum:Integer;
Flags:LongInt) : Boolean;
const
MaxSockets = 100;
FD_READ = 01;
FD_WRITE = 02;
FD_OOB = 04;
FD_ACCEPT = 08;
FD_CONNECT = 16;
FD_CLOSE = 32;
var
MySock : Array[0..MaxSockets] of TSocket;
implementation
{*** Winsock Funktionen *******************************************************}
{Winsock initialisieren}
function Winsock_Init : Boolean;
var
WSAData : TWSAData;
Cnt : Integer;
begin
for Cnt:=0 to MaxSockets do MySock[Cnt]:=0;
if WSAStartUp($0101, WSAData)=0 then Winsock_Init:=True
else Winsock_Init:=False;
end;
{Winsock beenden}
function Winsock_Terminate : Boolean;
begin
if WSACleanUp=0 then Winsock_Terminate:=True
else Winsock_Terminate:=False;
end;
{*** Socket Funktionen ********************************************************}
{Socket öffnen}
function Socket_Open(SockNum:Word) : Boolean;
begin
Socket_Open:=False;
if SockNum>MaxSockets then Exit;
MySock[SockNum]:=Socket(AF_INET,Sock_Stream,0);
if MySock[SockNum]Invalid_Socket then Socket_Open:=True
end;
{Socket schliessen}
function Socket_Close(SockNum:Word) : Boolean;
begin
if CloseSocket(MySock[SockNum])=0 then Socket_Close:=True
else Socket_Close:=False;
end;
{Socket verbinden}
function Socket_Connect(SockNum:Word;Port:Word;Addr:String) : Boolean;
var
SockAddr : TSockAddrIn;
begin
SockAddr.sin_family:=AF_INet;
SockAddr.sin_port:=htons(Port);
SockAddr.sin_zero:=#0#0#0#0#0#0#0#0;
SockAddr.sin_addr.s_addr:=INet_Addr(@Addr[1]);
if Connect(MySock[SockNum],SockAddr,SizeOf(TSockAddrIn))=0 then
Socket_Connect:=True
else Socket_Connect:=False;
end;
{Daten über Socket senden}
function Socket_Send(SockNum:Word; SendBuf:String) : Boolean;
begin
if Send(MySock[SockNum],SendBuf[1],Length(SendBuf),0)Socket_Error then
Socket_Send:=True
else Socket_Send:=False;
end;
{Daten über Socket empfangen}
function Socket_Receive(SockNum:Word; var RecBuf:String) : Boolean;
var
Count : LongInt;
begin
if ioctlsocket(MySock[SockNum],FIONREAD,Count)Socket_Error then
if Count>0 then
begin
SetLength(RecBuf,Count);
if Recv(MySock[SockNum],RecBuf[1],Count,0)Socket_Error then
begin
Socket_Receive:=True;
Exit;
end;
end;
RecBuf:=\'\';
Socket_Receive:=False;
end;
{*** Socket Funktionen für Server *********************************************}
{Socket an einen Port binden}
function Socket_Bind(SockNum:Byte;Port:Word) : Boolean;
var
SockAddr : TSockAddrIn;
begin
SockAddr.sin_family:=AF_INet;
SockAddr.sin_port:=htons(Port);
SockAddr.sin_zero:=#0#0#0#0#0#0#0#0;
SockAddr.sin_addr.S_Addr:=INADDR_ANY;
if Bind(MySock[SockNum],SockAddr,SizeOf(TSockAddrIn))=0 then
Socket_Bind:=True
else Socket_Bind:=False;
end;
{Serversocket-Parameter einstellen}
function Socket_Listen(SockNum:Word;MaxNumOfConn:Integer) : Boolean;
begin
if Listen(MySock[SockNum],MaxNumOfConn)=Socket_Error then
Socket_Listen:=False
else Socket_Listen:=True;
end;
{Client andocken}
function Socket_Accept(SockNum,SockNum2:Byte) : Boolean;
begin
MySock[SockNum2]:=Accept(MySock[SockNum],nil,nil);
if LongInt(MySock[SockNum2])>9999 then Socket_Accept:=False
else Socket_Accept:=True;
end;
{*** Zusätzliche Funktionen ***************************************************}
{Socket in asynchronen Modus schalten}
function Socket_Async(SockNum:Word;WndHandle:Integer;MsgNum:Integer;
Flags:LongInt) : Boolean;
begin
if WSAAsyncSelect(MySock[SockNum],WndHandle,MsgNum,Flags)=0
then Socket_Async:=True
else Socket_Async:=False;
end;end.
4.3 EasyChat (main.pas)
Das Beispielprogramm EasyChat ermöglicht es dem Anwender, mit bis zu 100 Leuten gleichzeitig über das Internet zu reden. EasyChat ist aber nicht nur ein Chat-Client, sondern auch ein Chatserver. Man ist also nicht auf einen äusseren Dienst angewiesen, sondern kann auf jedem Internet-PC dieses Programm im Servermodus laufen lassen. Den andern Teilnemern muss man dann nur noch die IP-Adresse des Servers bekannt geben, z.B. via Email, und es kann losgehen. (Wer seine eigene IP-Adresse nicht kennt, kann dies mit dem Windowstool "winipcfg.exe" herausfinden)
{*******************************************************}
{ }
{ EasyChat: Chat-Client/Server }
{ Facharbeit: \"TCP/IP: Kommunikation im Internet\" }
{ }
{ Copyright (c) 2000 by Martin Hüsser }
{ }
{*******************************************************}
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls, Menus, ComCtrls,
ToolWin, ExtCtrls, ImgList, Ws;
const
{Fensternachricht für SockComm Routine}
WM_SOCKCOMM = WM_USER+1;
{Message wird bei folgenden Socketereignissen ausgelöst:}
FD_CLIENT = FD_READ+FD_CONNECT+FD_CLOSE;
FD_SERVER = FD_CLIENT+FD_ACCEPT;
type
TMainForm = class(TForm)
{Controls}
MainMenu: TMainMenu;
Verbindung: TMenuItem;
Verbinden: TMenuItem;
Trennen: TMenuItem;
N1: TMenuItem;
Beenden: TMenuItem;
Bearbeiten: TMenuItem;
Ausschneiden: TMenuItem;
Kopieren: TMenuItem;
Einfuegen: TMenuItem;
Loeschen: TMenuItem;
Info: TMenuItem;
ToolBar: TToolBar;
ConnectButton: TToolButton;
StatusBar: TStatusBar;
SendMsgPanel: TPanel;
SendMsgEdit: TEdit;
Chat: TMenuItem;
Kicken: TMenuItem;
TimeOutTimer: TTimer;
N3: TMenuItem;
Aufzeichnen: TMenuItem;
AufzchngSaveDlg: TSaveDialog;
Servermodus: TMenuItem;
ToolbarIconList: TImageList;
ServerButton: TToolButton;
DisconnectButton: TToolButton;
ToolButton4: TToolButton;
CutButton: TToolButton;
CopyButton: TToolButton;
PasteButton: TToolButton;
ToolButton8: TToolButton;
Autor: TMenuItem;
Fluestern: TMenuItem;
KickenButton: TToolButton;
FluesternButton: TToolButton;
ChatMemo: TMemo;
{Initialisierung und Finalisierung}
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
{Design}
procedure FormResize(Sender: TObject);
procedure AufzeichnenClick(Sender: TObject);
procedure BeendenClick(Sender: TObject);
{Dialoge}
procedure AutorClick(Sender: TObject);
procedure FluesternClick(Sender: TObject);
procedure KickenClick(Sender: TObject);
{Werkzeugleiste}
procedure ConnectButtonClick(Sender: TObject);
procedure DisconnectButtonClick(Sender: TObject);
procedure ServerButtonClick(Sender: TObject);
procedure KickenButtonClick(Sender: TObject);
procedure FluesternButtonClick(Sender: TObject);
{Menu-Bearbeiten}
procedure AusschneidenClick(Sender: TObject);
procedure KopierenClick(Sender: TObject);
procedure EinfuegenClick(Sender: TObject);
procedure LoeschenClick(Sender: TObject);
{Verbindung}
procedure VerbindenClick(Sender: TObject);
procedure ServermodusClick(Sender: TObject);
{Kommunikation}
procedure SendMsgEditKeyPress(Sender: TObject; var Key: Char);
procedure TrennenClick(Sender: TObject);
{Ereignisbehandlung für Socket-Nachrichten}
procedure SockComm(var MsgInfo : TMessage); message WM_SOCKCOMM;
end;
var
MainForm : TMainForm;
{Steuervariablen}
ServerMode : Boolean;
NumOfConn : Integer;
{Variablen für Serverkommandos}
ServerCmd,
ServerParam : String;
{Speicherpläzte für Benutzernamen}
Nickname : String;
NickTable : Array[2..MaxSockets] of String;
implementation
{Dialoge einbinden}
uses verbinden, info, server, kicken, fluestern;
{$R *.DFM}
{*** Interne Tools ************************************************************}
{Sucht einen freien Socket}
function FindFreeSock : Integer;
var
Cnt : Integer;
begin
for Cnt:=2 to MaxSockets do
if MySock[Cnt]=0 then Break;
FindFreeSock:=Cnt;
end;
{Liefert die zum Handle in WParam passende Socketnummer}
function GetSockNr(WParam : Integer) : Integer;
var
Cnt : Integer;
begin
for Cnt:=0 to MaxSockets do
if MySock[Cnt]=WParam then Break;
if MySock[Cnt]=WParam then GetSockNr:=Cnt
else GetSockNr:=-1;
end;
{Interpretiert Steuernachrichten}
procedure ServerProcessMsg(var Buf : String; SockNum : Integer);
var
Index : Integer;
begin
ServerCmd:=\'\';
ServerParam:=\'\';
{Steuernachricht herausfiltern}
while Pos(\'¦\',Buf)>0 do
begin
ServerCmd:=UpCase(Buf[1]);
Index:=Pos(\'¦\',Buf);
ServerParam:=Copy(Buf,2,Index-2);
Delete(Buf,1,Index);
end;
{Nickname-Tabelle aktualisieren}
if Pos(\'>\',Buf)>0 then NickTable[SockNum]:=Copy(Buf,2,Pos(\'>\',Buf)-2);
end;
{*** Initialisierung und Finalisierung ****************************************}
{Konstruktor}
procedure TMainForm.FormCreate(Sender: TObject);
begin
{Winsock initialisieren}
Winsock_Init;
NumOfConn:=0;
ServerMode:=False;
end;
{Destruktor}
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
{Sind noch Verbindungen offen?}
if NumOfConn>0 then TrennenClick(MainForm);
{Winsock beenden}
Winsock_Terminate;
end;
{*** Form-Design **************************************************************}
{Passt die EditBox-Eingabezeile der Fenstergrösse an}
procedure TMainForm.FormResize(Sender: TObject);
begin
SendMsgEdit.Width:=MainForm.Width-8;
end;
{Terminiert das Programm bei Klick auf Menu-Beenden}
procedure TMainForm.BeendenClick(Sender: TObject);
begin
Application.Terminate;
end;
{Setzt oder löscht das Häckchen beim Untermenu Aufzeichnung}
procedure TMainForm.AufzeichnenClick(Sender: TObject);
begin
Aufzeichnen.Checked:=not(Aufzeichnen.Checked);
end;
{*** Dialoge ******************************************************************}
{Zeigt ein Dialogfenster mit Informationen an}
procedure TMainForm.AutorClick(Sender: TObject);
begin
InfoDlg.ShowModal;
end;
{Zeigt den Flüster-Dialog an}
procedure TMainForm.FluesternClick(Sender: TObject);
var
Cnt : Integer;
begin
{Wurde der Cancle-Knopf gedrückt?}
if FluesternDlg.ShowModal=mrCancel then Exit;
if ServerMode then
begin
{Sende Flüsternachricht an die betreffenden Clients}
for Cnt:=2 to MaxSockets do
if NickTable[Cnt]=FluesternUser then
Socket_Send(Cnt,FluesternMsg);
ChatMemo.Lines.Add(\'(1)\'+FluesternMsg);
end
else begin
{Sende Flüsterkommando an Server}
Socket_Send(0,\'F\'+FluesternUser+\'¦\'+FluesternMsg);
ChatMemo.Lines.Add(FluesternMsg);
end;
end;
{Zeigt den Kicken-Dialog an}
procedure TMainForm.KickenClick(Sender: TObject);
var
Cnt : Integer;
begin
if KickenDlg.ShowModal=mrCancel then Exit;
for Cnt:=2 to MaxSockets do
if NickTable[Cnt]=KickUser then
begin
{Client kicken!}
Socket_Close(Cnt);
MySock[Cnt]:=0;
Dec(NumOfConn);
StatusBar.Panels[0].Text:=
IntToStr(Cnt)+\' Benutzer eingeloggt\';
end;
end;
{*** Werkzeugleiste ***********************************************************}
procedure TMainForm.ConnectButtonClick(Sender: TObject);
begin
VerbindenClick(MainForm);
end;
procedure TMainForm.ServerButtonClick(Sender: TObject);
begin
ServermodusClick(MainForm);
end;
procedure TMainForm.DisconnectButtonClick(Sender: TObject);
begin
TrennenClick(MainForm);
end;
procedure TMainForm.KickenButtonClick(Sender: TObject);
begin
KickenClick(MainForm);
end;
procedure TMainForm.FluesternButtonClick(Sender: TObject);
begin
FluesternClick(MainForm);
end;
{*** Menu: Bearbeiten *********************************************************}
{Schneidet den selektierten Text aus und kopiert ihn in die Zwischenablage}
procedure TMainForm.AusschneidenClick(Sender: TObject);
begin
if MainForm.ActiveControl=ChatMemo then
ChatMemo.CopyToClipboard;
if MainForm.ActiveControl=SendMsgEdit then
SendMsgEdit.CutToClipboard;
end;
{Kopiert den selektierten Text in die Zwischenablage}
procedure TMainForm.KopierenClick(Sender: TObject);
begin
if MainForm.ActiveControl=ChatMemo then
ChatMemo.CopyToClipboard;
if MainForm.ActiveControl=SendMsgEdit then
SendMsgEdit.CopyToClipboard;
end;
{Fügt den Text aus der Zwischenablage an der aktuellen Cursorposition ein}
procedure TMainForm.EinfuegenClick(Sender: TObject);
begin
if MainForm.ActiveControl=SendMsgEdit then
SendMsgEdit.PasteFromClipboard;
end;
{Löscht den selektierten Text}
procedure TMainForm.LoeschenClick(Sender: TObject);
begin
if MainForm.ActiveControl=SendMsgEdit then
SendMsgEdit.ClearSelection;
end;
{*** Verbinden ****************************************************************}
{Verbindet den Client mit einem Server}
procedure TMainForm.VerbindenClick(Sender: TObject);
begin
{Verbindungsdaten einholen}
if VerbindenDlg.ShowModal=mrCancel then Exit;
Nickname:=ClientNick;
{Verbindung aufbauen}
Socket_Open(0);
Socket_Async(0,Application.MainForm.Handle,WM_SOCKCOMM,FD_CLIENT);
Socket_Connect(0,ClientPort,ClientAddr);
{Design}
StatusBar.Panels[0].Text:=\'Verbindungsaufbau...\';
Verbinden.Enabled:=False;
ConnectButton.Enabled:=False;
ServerModus.Enabled:=False;
ServerButton.Enabled:=False;
Trennen.Enabled:=True;
DisconnectButton.Enabled:=True;
end;
{Schaltet in den Servermodus}
procedure TMainForm.ServermodusClick(Sender: TObject);
begin
{Serverinformationen einholen}
if ServerDlg.ShowModal=mrCancel then Exit;
Nickname:=ServerNick;
{Server auf Empfang schalten}
Socket_Open(1);
Socket_Async(1,Application.MainForm.Handle,WM_SOCKCOMM,FD_SERVER);
Socket_Bind(1,ServerPort);
Socket_Listen(1,5);
ServerMode:=True;
{Design}
Servermodus.Enabled:=False;
ServerButton.Enabled:=False;
Verbinden.Enabled:=False;
ConnectButton.Enabled:=False;
Trennen.Enabled:=True;
DisconnectButton.Enabled:=True;
StatusBar.Panels[0].Text:=\'0 Benutzer eingeloggt\';
end;
{*** Kommunikation ************************************************************}
{Sendet eine Nachricht an den Server bzw. an die verbundenen Clients}
procedure SendMsg(ExcludeSock : Integer; Msg : String);
var
Cnt : Integer;
begin
{Nachricht an Server senden}
if Servermode=False then Socket_Send(0,Msg)
{Nachricht an die Clients senden}
else begin
for Cnt:=2 to MaxSockets do
if CntExcludeSock then
if MySock[Cnt]0 then
Socket_Send(Cnt, Msg);
end;
end;
{Übergibt beim Drücken der Entertaste die eingegebene Meldung an SendMsg}
procedure TMainForm.SendMsgEditKeyPress(Sender: TObject; var Key: Char);
var
Buf : String;
begin
{Wenn keine Verbindung, Eingabe unterdrücken}
if NumOfConn=0 then Key:=#0;
{Wurde Enter gedrückt?}
if Key=#13 then
begin
if SendMsgEdit.Text=\'\' then Exit;
{Steuerzeichen herausfiltern}
Buf:=SendMsgEdit.Text;
while Pos(\'¦\',Buf)>0 do
Delete(Buf,Pos(\'¦\',Buf),1);
{Nicknamen voranstellen}
Buf:=\' \'+Buf;
{Nachricht senden}
SendMsg(0,Buf);
{Design}
if ServerMode then Buf:=\'(1)\'+Buf;
ChatMemo.Lines.Add(Buf);
Key:=#0;
SendMsgEdit.Text:=\'\';
end;
end;
{Trennt die Verbindung mit dem Server bzw. den Clients}
procedure TMainForm.TrennenClick(Sender: TObject);
var
Cnt : Integer;
begin
{Sockets schliessen}
if ServerMode=False then begin Socket_Close(0); MySock[0]:=0; end
else begin
for Cnt:=2 to MaxSockets do
if MySock[Cnt]0 then
begin
Socket_Close(Cnt);
MySock[Cnt]:=0;
end;
Socket_Close(1);
end;
NumOfConn:=0;
{Design}
SendMsgEdit.Text:=\'\';
StatusBar.Panels[0].Text:=\'Verbindung getrennt\';
{Aufzeichnung speichern?}
if Aufzeichnen.Checked then
if AufzchngSaveDlg.Execute then
ChatMemo.Lines.SaveToFile(AufzchngSaveDlg.FileName);
{Design}
ChatMemo.Clear;
Verbinden.Enabled:=True;
ConnectButton.Enabled:=True;
Servermodus.Enabled:=True;
ServerButton.Enabled:=True;
Trennen.Enabled:=False;
DisconnectButton.Enabled:=False;
Fluestern.Enabled:=False;
Kicken.Enabled:=False;
Servermode:=False;
end;
{*** Socket-Ereignisbehandlungsroutine ****************************************}
{Ereignisbehandlungsroutine für sämtliche geöffneten Sockets}
procedure TMainForm.SockComm(var MsgInfo : TMessage);
var
Buf : String;
SockNum,
FreeSock,
Cnt : Integer;
begin
{Nummer des Sockets herausfinden, der die Fensternachricht ausgeöst hat}
SockNum:=GetSockNr(MsgInfo.WParam);
if SockNum=-1 then Exit;
{Clientmodus}
if SockNum=0 then
begin
case MsgInfo.LParam of
FD_CONNECT : {Client ist verbunden}
begin
StatusBar.Panels[0].Text:=\'Verbunden\';
NumOfConn:=1;
Fluestern.Enabled:=True;
FluesternButton.Enabled:=True;
end;
FD_READ : {Client empfängt Daten vom Server}
begin
{Daten entgegennehmen und anzeigen}
Socket_Receive(0,Buf);
ChatMemo.Lines.Add(Buf);
end;
FD_CLOSE : {Server hat die Verbindung geschlossen}
begin
{Socket schliessen}
TrennenClick(MainForm);
Fluestern.Enabled:=False;
FluesternButton.Enabled:=False;
end;
end;
end;
{Servermodus: Serversocket}
if SockNum=1 then
begin
case MsgInfo.LParam of
FD_ACCEPT : {Ein Client dockt an}
begin
{Client andocken und neuen Socket zuweisen}
FreeSock:=FindFreeSock;
Socket_Accept(1,FreeSock);
NickTable[FreeSock]:=\'\';
{Maximale Anzahl Benuter darf nicht überschritten werden}
if NumOfConn=MaxUser then
begin
Socket_Close(FreeSock);
MySock[FreeSock]:=0;
Exit;
end;
{Design}
Inc(NumOfConn);
StatusBar.Panels[0].Text:=IntToStr(NumOfConn)+\' Benutzer eingeloggt\';
Kicken.Enabled:=True;
KickenButton.Enabled:=True;
Fluestern.Enabled:=True;
FluesternButton.Enabled:=True;
end;
end;
end;
{Servermodus}
if SockNum>1 then
begin
case MsgInfo.LParam of
FD_CLOSE : {Ein Client hat die Verbindung getrennt}
begin
{Socket schliessen}
Socket_Close(SockNum);
MySock[SockNum]:=0;
Dec(NumOfConn);
{Design}
StatusBar.Panels[0].Text:=IntToStr(NumOfConn)+\' Benutzer eingeloggt\';
if NumOfConn=0 then
begin
Kicken.Enabled:=False;
KickenButton.Enabled:=False;
Fluestern.Enabled:=False;
FluesternButton.Enabled:=False;
end;
end;
FD_READ : {Server empfängt die Daten eines Clients}
begin
{Daten entgegennehmen}
Socket_Receive(SockNum,Buf);
{verarbeiten}
ServerProcessMsg(Buf,SockNum);
{Flüster-Nachricht?}
if ServerCmd=\'F\' then
begin
if ServerParam=Nickname then
ChatMemo.Lines.Add(\'(\'+IntToStr(SockNum)+\')\'+Buf)
else for Cnt:=2 to MaxSockets do
if CntSockNum then
if NickTable[Cnt]=ServerParam then
Socket_Send(Cnt,Buf);
Exit;
end;
{an die anderen Clients weiterleiten}
SendMsg(SockNum,Buf);
{und anzeigen}
ChatMemo.Lines.Add(\'(\'+IntToStr(SockNum)+\')\'+Buf);
end;
end;
end;
end;
end.
|