Einzelnen Beitrag anzeigen

Michael II

Registriert seit: 1. Dez 2012
Ort: CH BE Eriswil
763 Beiträge
 
Delphi 11 Alexandria
 
#21

AW: Socket-Verbindung herstellen ohne zu blockieren

  Alt 11. Mai 2018, 02:14
Die endgültige Lösung war bei mir dann übrigens der Umstieg auf die Overbyte ICS Komponenten, die funktionieren asynchron (und daher ohne Hänger beim Verbindgen etc.). Wenn das für dich eine Option wäre, kann ich ja hier mal ein Beispiel für die Ansteuerung anhängen.
Das Problem ist zwar gelöst, aber da ich die ICS bisher nicht kenne, würde mich Dein Beispiel interessieren.

Tipp: "ICS Overbyte" kannst du in neueren Delphis direkt über dein Delphi, dort über "Tools > GetItPackageManager, ICS Overbyte" installieren.

Mit Overbyte ist alles Ereignis gesteuert. Du kannst zum Beispiel direkt auf OnChangeState des Sockets reagieren: Wenn dir via das Ereignis TWSocket.OnChangeState signalisiert wird, dass dein Socket neu geschlossen ist (wsClosed), kannst du den Wiederaufbau einleiten.


Beispiel (Code unten).
Drei Sockets: ListenSocket, SocketB und SocketA. Ziel: Aufbau einer Verbindung SocketA <-> SocketB.

Beim Starten der App geht der ListenSocket auf wsListening. (FormCreate)

Drück den Button „Verbinde“. SocketA leitet den Verbindungsaufbau zu ListenSocket ein (Verbinde).

ListenSocket: Das Ereignis OnSessionAvailable wird ausgelöst (ListenSocketSessionAvailable). ListenSocket akzeptiert die Verbindung (Accept) und SocketB übernimmt diese (Dup). SocketA und SocketB sind nun miteinander verbunden.

Zweites Ziel: Wiederaufbau der Leitung, falls SocketA.SocketState = wsClosed:
Drück den Button „Schliessen“. SocketA oder SocketB werden geschlossen.

Via das Ereignis OnChangeState (SocketAChangeState) werden Änderungen des SocketStates signalisiert. Sollte der SocketState von SocketA neu wsClosed sein, leiten wir den Wiederaufbau der Verbindung SocketA -> (ListenSocket ->) Socket B ein.

Im Memo werden alle Änderungen der SocketStates angezeigt.


Drittes Ziel: Sende einen String von A nach B.
Mittels SocketA.SendStr( s ) versendest du einen String s.

Im Ereignis OnDataAvailable von SocketB wird der Empfang von Daten angezeigt. Die Daten können zum Beispiel mit SocketB.ReceiveStr ausgelesen werden.


[ In der Praxis kann es hilfreich sein, periodisch ein „Ping-Paket“ über die aufgebaute Leitung zu senden und die Verbindung neu aufzubauen, wenn das Paket „zu lange“ nicht quittiert wird.
Wahrscheinlich würdest du zu sendende Daten in einen Buffer schreiben und erst dann löschen, wenn die Gegenstelle den Empfang der Daten bestätigt hat.]

Delphi-Quellcode:
unit Unit38;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, OverbyteIcsWndControl,
  OverbyteIcsWSocket, Vcl.StdCtrls;


const WM_MEINEMELDUNG = WM_APP + 1;

type
  TForm38 = class(TForm)
    SocketA: TWSocket;
    ListenSocket: TWSocket;
    SocketB: TWSocket;
    VerbindeButton: TButton;
    SchliessenButton: TButton;
    Memo1: TMemo;
    SendeString: TButton;
    procedure FormCreate(Sender: TObject);
    procedure VerbindeButtonClick(Sender: TObject);
    procedure ListenSocketSessionAvailable(Sender: TObject; ErrCode: Word);
    procedure SocketAChangeState(Sender: TObject; OldState,
      NewState: TSocketState);
    procedure SchliessenButtonClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure SendeStringClick(Sender: TObject);
    procedure SocketBDataAvailable(Sender: TObject; ErrCode: Word);
  private
    { Private-Deklarationen }
    procedure Machetwas(var Message: TMessage); message WM_MEINEMELDUNG;
  public
    { Public-Deklarationen }
    procedure Verbinde;
  end;

var
  Form38: TForm38;

implementation

{$R *.dfm}

uses System.TypInfo;

var appschliesst : boolean = false;

function socketstr( hss : TSocketState ) : string;
begin
  { Wert von hss in String umwandeln }
  Result := GetEnumName(typeinfo(TSocketState),ord(hss));
end;


procedure TForm38.Machetwas(var Message: TMessage);
begin
  case Message.WParam of
  0 : begin
        { Störung simulieren: SocketA oder Gegenstelle SocketB schliessen }
        Memo1.Lines.Add('Entweder SocketA oder SocketB schliessen');
        case random(2) of
        0: SocketA.Close;
        1: SocketB.Close;
        end;
      end;
      { SocketA verbindet neu mit der Gegenstelle }
  1 : Verbinde;
  end;
end;


procedure TForm38.SchliessenButtonClick(Sender: TObject);
begin
   PostMessage( Handle, WM_MEINEMELDUNG, 0, 0 );
end;


procedure TForm38.VerbindeButtonClick(Sender: TObject);
begin
  Verbinde;
end;

{ ListenSocket geht in den SocketState wsListening über: }

procedure TForm38.FormCreate(Sender: TObject);
begin
  ListenSocket.Addr := '0.0.0.0';
  ListenSocket.Port := '7001';
  ListenSocket.Proto := 'tcp';
  ListenSocket.Listen;

  ListenSocket.OnChangeState := SocketAChangeState;
  SocketA.OnChangeState := SocketAChangeState;
  SocketB.OnChangeState := SocketAChangeState;

  ListenSocket.OnSessionAvailable := ListenSocketSessionAvailable;

  SocketB.OnDataAvailable := SocketBDataAvailable;
end;


{ SocketA soll mit ListenSocket verbinden: }

procedure TForm38.Verbinde;
begin
  Memo1.Lines.Add('SocketA Verbindungsaufbau');
  SocketA.Close;
  SocketA.Addr := '127.0.0.1';
  SocketA.proto := 'tcp';
  SocketA.Port := '7001';
  SocketA.Connect;
end;


{ OnSessionAvailable:
  ListenSocket akzeptiert die Verbindung, SocketB übernimmt (Dup) - SocketA und SocketB sind miteinander verbunden }


procedure TForm38.ListenSocketSessionAvailable(Sender: TObject; ErrCode: Word);
var h : THandle;
begin
    h := ListenSocket.Accept;
    SocketB.Dup( h );
end;


{ ChangeState:
  Änderung SocketState }


procedure TForm38.SocketAChangeState(Sender: TObject; OldState,
  NewState: TSocketState);
begin
    if appschliesst then
    begin
      if ( SocketA.State = wsclosed ) and ( SocketB.State = wsClosed ) then close;
    end
    else
    begin
      Memo1.Lines.Add( (Sender as TWSocket).Name + ' ' + socketstr(OldState) + '->' + socketstr(NewState) );

      if ( Sender = SocketA ) then
      if ( OldState = wsConnected ) and ( NewState = wsClosed ) then
      PostMessage( Handle, WM_MEINEMELDUNG, 1, 0 );
    end;
end;


{ Beispiel: SocketA sendet einen String an SocketB }

procedure TForm38.SendeStringClick(Sender: TObject);
begin
  if SocketA.State = wsConnected then
  SocketA.SendStr( Memo1.Text );
end;

{ SockezB empfängt den String}

procedure TForm38.SocketBDataAvailable(Sender: TObject; ErrCode: Word);
var empfangen : string;
begin
  empfangen := SocketB.ReceiveStr;
  if empfangen <> 'then
  ShowMessage( empfangen );
end;


{ Die App soll geschlossen werden...
  Wenn SocketA oder SocketB nicht geschlossen sind => canclose:= false =>
  Im SocketAChangeState wird Close aufgerufen, sobald SocketA und SocketB geschlossen sind }


procedure TForm38.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  {.....}
  if canclose then
  begin
      appschliesst := true;
      ListenSocket.Close;
      SocketA.CloseDelayed;
      SocketB.CloseDelayed;
      canclose := ( SocketA.State = wsclosed ) and ( SocketB.State = wsClosed );
  end;
end;

end.
Angehängte Dateien
Dateityp: zip overbyte.zip (53,8 KB, 15x aufgerufen)
Michael Gasser
  Mit Zitat antworten Zitat