Multidirektionale Kommunikation

Alle Fragen zur Netzwerkkommunikation
Antworten
Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Multidirektionale Kommunikation

Beitrag von Scotty »

Ich habe basierend auf einem Codeschnipsel von Theo eine Multiclient-Client-Kommunikation gebastelt und würde Euch bitten, da mal einen kritischen Blick drauf zu werfen. Prinzipiell scheint es zu funktionieren, aber ich bin für jede Art von Kritik dankbar :roll:

Code: Alles auswählen

uses {Synapse}
type
 
    TOnReceived=procedure(aValue:String) of object;
 
     { TTCPServer }
 
     TTCPServer=class(TThread)
       private
         FServerThreadList : TThreadList;
         FServerSocket     : TTCPBlockSocket;
         FPort       : string;
         FMaxThreads : integer;
         procedure ShutDownThreads;
       public
         constructor Create;
         destructor Destroy;
         procedure Execute; override;
         procedure StopSock;
       end;
 
     { TTCPServerThread }
 
     TTCPServerThread=class(TThread)
       private
         FSocket     : TTCPBlockSocket;
         FSocketNum  : integer;
         FThreadList : TThreadList;
       public
         destructor Destroy; override;
         procedure Execute; override;
         property SocketNumber: integer read FSocketNum write FSocketNum;
         property ThreadList: TThreadList read FThreadList write FThreadList;
         property Socket : TTCPBlockSocket read FSocket;
       end;
 
     { TTCPClient }
 
     TTCPClient=class(TThread)
       private
         FSocket     : TTCPBlockSocket;
         FDataOut    : string;
         FDataIn     : string;
         FOnReceived : TOnReceived;
         procedure SyncMethod;
       public
         constructor Create(aIP,aPort:string);
         destructor Destroy; override;
         procedure Execute; override;
         property Send:string read FDataOut write FDataOut;
         property OnReceive:TOnReceived read FOnReceived write FOnReceived;
       end;
 
{ TTCPServer }
 
constructor TTCPServer.Create;
begin
  inherited Create(true);
  FPort:='1234';
  FMaxThreads:=4;
  FServerThreadList:=TThreadList.Create;
  FServerThreadList.Duplicates:=dupAccept;
  FServerSocket:=TTCPBlockSocket.Create;
  FServerSocket.EnableReuse(true);
  FreeOnTerminate:=true;
  Resume;
end;
 
destructor TTCPServer.Destroy;
begin
  if assigned(FServerSocket) then
  begin
    FServerSocket.CloseSocket;
    FServerSocket.Free;
  end;
  FServerThreadList.UnlockList;
  FServerThreadList.Free;
  inherited;
end;
 
procedure TTCPServer.ShutDownThreads;
var i : integer;
begin
  with FServerThreadList.LockList do
  try
    for i:=0 to Count-1 do
    with TTCPServerThread(Items[i]) do
    begin
      StopSock;
      Terminate;
      ThreadList:=nil;
    end;
  finally
    FServerThreadList.UnlockList;
  end;
end;
 
procedure TTCPServer.Execute;
var FServerThread : TTCPServerThread;
    NumThreads    : integer;
begin
  if assigned(FServerSocket) then
  begin
    FServerSocket.Bind('0.0.0.0', FPort);
    if FServerSocket.LastError=0 then
    begin
      FServerSocket.Listen;
      while not Terminated do
      begin
        if FServerSocket.CanRead(1000) then
        begin
          try
            NumThreads:=FServerThreadList.LockList.Count;
          finally
            FServerThreadList.UnlockList;
          end;
          if NumThreads<FMaxThreads then
          begin
            try
              FServerThread:=TTCPServerThread.Create(true);
              FServerThread.FreeOnTerminate:=true;
              FServerThread.ThreadList:=FServerThreadList;
              FServerThread.SocketNumber:=FServerSocket.Accept;
              FServerThreadList.Add(FServerThread);
              FServerThread.Resume;
            except
              if assigned(FServerThread) then FServerThread.Terminate;
            end;
          end;
        end;
      end;//Terminated
    end else Terminate;//FSocket.LastError<>0
  end;
  if assigned(FServerSocket) then FServerSocket.StopFlag:=true;
  ShutDownThreads;
end;
 
procedure TTCPServer.StopSock;
begin
  if FServerSocket<>nil then
  begin
    FServerSocket.StopFlag := true;
    FServerSocket.AbortSocket;
  end;
end;
 
{ TTCPServerThread }
 
destructor TTCPServerThread.Destroy;
begin
  if FThreadList<>nil then FThreadList.Remove(self);
  inherited;
end;
 
procedure TTCPServerThread.Execute;
var i:integer;
    s:string;
begin
  FSocket:=TTCPBlockSocket.Create;
  try
    FSocket.Socket:=FSocketNum;
    FSocket.SendString('connected: '+inttostr(FSocketNum)+CRLF);
    while not Terminated do
    begin
      s:=FSocket.RecvString(10000);
      if s<>'' then
      begin
        with FThreadList.LockList do
        try
          for i:=0 to Count-1 do
            TTCPServerThread(Items[i]).Socket.SendString(s+CRLF);
        finally
          FThreadList.UnlockList;
        end;
        s:='';
      end;
      if not (FSocket.LastError in [0,110]) then Terminate; //110=WSAETIMEDOUT
    end;
  finally
    FSocket.CloseSocket;
    FreeAndNil(FSocket);
  end;
end;
 
{ TTCPClient }
 
procedure TTCPClient.SyncMethod;
begin
  if assigned(FOnReceived) then FOnReceived(FDataIn);
end;
 
constructor TTCPClient.Create(aIP, aPort: string);
begin
  inherited Create(true);
  FreeOnTerminate:=true;
  FSocket:=TTCPBlockSocket.Create;
  FSocket.Connect(aIP,aPort);
  Resume;
end;
 
destructor TTCPClient.Destroy;
begin
  if assigned(FSocket) then
  begin
    FSocket.CloseSocket;
    FreeAndNil(FSocket);
  end;
  inherited Destroy;
end;
 
procedure TTCPClient.Execute;
begin
  while not Terminated do
  begin
    if FDataOut<>'' then
    begin
      FSocket.SendString(FDataOut+CRLF);
      FDataOut:='';
    end;
    FDataIn:=FSocket.RecvString(100);
    if FDataIn<>'' then
    begin
      Synchronize(@SyncMethod);
      FDataIn:='';
    end;
    if not (FSocket.LastError in [0,110]) then Terminate; //110=WSAETIMEDOUT
  end;
end;

Code: Alles auswählen

TCPServer:=TTCPServer.Create;
TCPClient:=TTCPClient.Create('127.0.0.1','1234');
TCPClient.Send:='Test';

Benutzeravatar
theo
Beiträge: 10898
Registriert: Mo 11. Sep 2006, 19:01

Re: Multidirektionale Kommunikation

Beitrag von theo »

Scotty hat geschrieben:Ich habe basierend auf einem Codeschnipsel von Theo
Boah, wo hast du denn den Link ausgebuddelt? ;-)

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Multidirektionale Kommunikation

Beitrag von Scotty »

Dacht ich mir, dass Du darüber staunst :shock:
Hobby-Programmierer schreiben ja keinen Code, sie googeln danach: http://www.mail-archive.com/synalist-pu ... 00217.html" onclick="window.open(this.href);return false; . Viel Brauchbares gefunden habe ich jedoch nicht, außer Deinem Beispiel. Ich habe aus dem Schnipsel alles raus genommen, was ich nicht verstehe und den Client in eine Klasse verpackt. Unsicher bin ich mir zum Beispiel, ob die timeouts sinnvoll sind und das Abmelden der Clients scheint noch etwas Arbeit notwendig zu haben.

Respekt, was Du alles schon gemacht hast!

PS: Ich habe gestern Lazarus und FPC von 0.9.27, 2.3.1 per SVN auf 0.9.29 und 2.5.1 aktualisiert und musste viele Funktionen von Synapse umschreiben. Zum Beispiel:

Code: Alles auswählen

function SendTo(s: TSocket; Buf: TMemory; len, flags: Integer; addrto: TVarSin): Integer;
begin
//  Result := sockets.SendTo(s, Buf^, len, flags, addrto, SizeOfVarSin(addrto));
  Result := sockets.fpSendTo(s, @Buf^, len, flags, @addrto, SizeOfVarSin(addrto));
end;
Gibt es eine elegantere Lösung, {$ifdef legacysocket} (-> socketsh.inc) global für mein Projekt zu definieren, solange Synapse noch nicht aktualisiert ist?

Benutzeravatar
theo
Beiträge: 10898
Registriert: Mo 11. Sep 2006, 19:01

Re: Multidirektionale Kommunikation

Beitrag von theo »

Scotty hat geschrieben:Unsicher bin ich mir zum Beispiel, ob die timeouts sinnvoll sind und das Abmelden der Clients scheint noch etwas Arbeit notwendig zu haben.
Ist lange her..., wieso meinst du, dass das Abmelden noch Arbeit benötigt?
Bei den Timeouts würde ich mich mal auf ein paar Tests verlassen.
Scotty hat geschrieben: PS: Ich habe gestern Lazarus und FPC von 0.9.27, 2.3.1 per SVN auf 0.9.29 und 2.5.1 aktualisiert und musste viele Funktionen von Synapse umschreiben.
Ja, das nervt. Lukas macht bisher keine Anstalten, das zu ändern.
Falls du das Ganze im Griff hast, könntest du das umsetzen? Lukas Gebauer würde sich bestimmt freuen, und wir auch :D

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Multidirektionale Kommunikation

Beitrag von Scotty »

Falls du das Ganze im Griff hast, könntest du das umsetzen?
Es war nicht sonderlich schwierig, die Legacy-Prozeduren durch fp*-Prozeduren zu ersetzen. Die quick 'n dirty-Version hängt an (nur unter Linux geprüft). Lieber wäre mir aber, ich wüsste, wie ein globales, projektbezogenes $define gemacht werden kann. Dann müsste man keine Änderung an Synapse vornehmen.
Dateianhänge
ssfpc.pas.tar.gz
(6.99 KiB) 105-mal heruntergeladen

Hitman
Beiträge: 512
Registriert: Mo 25. Aug 2008, 18:17
OS, Lazarus, FPC: ArchLinux x86, WinVista x86-64, Lazarus 0.9.29, FPC 2.4.1
CPU-Target: x86
Wohnort: Chemnitz

Re: Multidirektionale Kommunikation

Beitrag von Hitman »

Einfach dem Compiler -dDEFINENAME übergeben .... (aufheben eines defines mit -uDEFINENAME).

Benutzeravatar
theo
Beiträge: 10898
Registriert: Mo 11. Sep 2006, 19:01

Re: Multidirektionale Kommunikation

Beitrag von theo »

Scotty hat geschrieben: Die quick 'n dirty-Version hängt an
Cool!
Was heisst quick 'n dirty? Gibt's noch Baustellen?

P.S. kannst dein Werk ja hier noch bekannt machen. Da hat schon einer danach gefragt. ;-)
http://sourceforge.net/mailarchive/foru ... ist-public" onclick="window.open(this.href);return false;

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Multidirektionale Kommunikation

Beitrag von Scotty »

Hitman hat geschrieben:Einfach dem Compiler -dDEFINENAME übergeben ....
Das habe ich probiert, denke ich. Allein -dlegacysocket hat keine Änderungen gebracht.
Theo hat geschrieben:Was heisst quick 'n dirty?
Ich habe mir keine Gedanken darüber gemacht, ob die Rückgabewerte stimmen. Es ging mir lediglich darum, kompilieren zu können und meine Routinen lokal zu testen.

Hitman
Beiträge: 512
Registriert: Mo 25. Aug 2008, 18:17
OS, Lazarus, FPC: ArchLinux x86, WinVista x86-64, Lazarus 0.9.29, FPC 2.4.1
CPU-Target: x86
Wohnort: Chemnitz

Re: Multidirektionale Kommunikation

Beitrag von Hitman »

Du solltest beim kompilieren mit geänderten Defines ggf. -B (Build All) nutzen ... soweit ich weiß erkennt der compiler nicht, welche Konsequenzen das setzen eines Defines hat (sprich: er kompiliert nicht alles neu, was er neu kompilieren müsste)

Scotty
Beiträge: 768
Registriert: Mo 4. Mai 2009, 13:24
OS, Lazarus, FPC: Arch Linux, Lazarus 1.3 r44426M FPC 2.6.4
CPU-Target: x86_64-linux-qt/gtk2
Kontaktdaten:

Re: Multidirektionale Kommunikation

Beitrag von Scotty »

Unter Compilereinstellungen für Projekt->Andere->Benutzerdefinierte Einstellungen steht jetzt -dlegacysocket.
Einstellungen anzeigen liefert:

Code: Alles auswählen

-MObjFPC -Sgi -CirotR -O1 -gl -WG -vewnhi -l -FuSynapse/ ... -dlegacysocket -dUseCThreads -dLCL -dLCLgtk2 -dUseCThreads
Ich kompiliere per Menü->Start->Alles kompilieren und bekomme zum Beispiel hier einen Fehler:

Code: Alles auswählen

Result := sockets.GetPeerName(s, name, Len);
//Synapse/ssfpc.pas(549,32) Error: Identifier not found "GetPeerName"
Hingegen funktioniert das Folgende

Code: Alles auswählen

Result := sockets.fpGetPeerName(s, @name, @Len);
Unter socketsh.inc steht das hier (gefunden per Ctrl-Linksklick auf fpgetpeername):

Code: Alles auswählen

function  fpgetpeername (s:cint; name  : psockaddr; namelen : psocklen):cint;
...
{$ifdef legacysocket}
...
Function GetPeerName(Sock:Longint;Var Addr;Var Addrlen:Longint):Longint;deprecated;
{$endif}
Was mache ich falsch?

Edit: Na klar, socketsh.inc ist ein Teil von Lazarus (oder FPC) und wird ja nicht neu kompiliert. Dort ist demzufolge der Schalter nicht gesetzt. :oops: Wenn ich meine Funktion mit {$ifdef legacysocket} kapsle, dann meckert der Compiler. Ergo ist der Schalter für mein Projekt gesetzt. Die richtige Frage ist also: Wie bekomme ich den Switch in Lazarus rein?

Antworten