Autor Beitrag
Peter18
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Mi 22.04.15 17:30 
Ein freundliches Hallo an alle!

Ich habe ein Objekt erstellt um über Netzwerk Daten austauschen zu können. Für Accept und Listen habe ich Threads erstellt. Sah alles ganz gut aus, obwohl es meine ersten Threads sind. Doch plötzlich zeigte sich ein seltsames Verhalten: Mit jeder Programmänderung ein neues Verhalten.

Da ich den Begriff "Threadsicher" bei der Beschäftigung mit dem Thema mehrfach gelesen habe und nicht sicher bin, ob meine Treads korekt sind, bitte ich darum sie zu begutachten.

Ich habe zum Warten auf Clientverbindungen folgenden Faden erstellt:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
unit TcpSrvAccept;

interface

uses
  Classes, Winsock,
  {eigene}
  TCP_IP, WinSock2;

type
  T_TcpSrvAccept = class(TThread)
  private
    { Private-Deklarationen}
    oSSock     : TSocket       ;
    oSock      : TSocket       ;
    oOnAccept  : T_AcceptEvent ;
    oInd       : Integer       ;

  protected
    procedure Execute; override;
    procedure Synchronize;

  public
    Constructor Create(     OnAcc : T_AcceptEvent;
                        var Sock  : TSocket      ;
                            Ind   : Integer        );
    procedure Terminate;

  end;

implementation

// ################################################################# Constructor

Constructor T_TcpSrvAccept.Create(     OnAcc : T_AcceptEvent;
                                   var Sock  : TSocket      ;
                                       Ind   : Integer        );
begin
  oOnAccept  := OnAcc         ;
  oSSock     := Sock          ;
  oSock      := INVALID_SOCKET;
  oInd       := Ind           ;
  inherited create( false )   ;
  Priority        := tpLower  ;
  FreeOnTerminate := true     ;
end;

// ################################################################# Constructor
// ################################################################# Synchronize

procedure T_TcpSrvAccept.Synchronize;
begin
  if Assigned( oOnAccept ) then oOnAccept( oSock, oInd );
end;

// ################################################################# Synchronize
// ##################################################################### Execute

procedure T_TcpSrvAccept.Execute;
begin
  oSock := accept( oSSock, NilNil );
  Synchronize;
end;

// ##################################################################### Execute
// ################################################################### Terminate

procedure T_TcpSrvAccept.Terminate;
begin
  inherited Terminate;
end;

// ################################################################### Terminate

end.

Die Ereignisroutine "oOnAccept" trägt Socket und Buffer in einem Array ein und startet den Faden neu, um weitere Clients versorgen zu können.

Für den Datenaustausch habe ich einen zweiten Tread erstellt:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
unit Clnt_Listen;

interface

uses
  SysUtils, Classes, winsock,
  { eigene }
  TCP_IP;

type
  T_ClntListen = class(TThread)
  private
    { Private-Deklarationen}
    oSock      : TSocket       ;
    oOnReceive : T_ReceiveEvent;
    oBuffer    : AnsiString    ;
    oIndex     : Integer       ;

  protected
    procedure Execute;                                                 override;
    procedure Synchronize( Buffer : AnsiString; Res : Integer );

  public
    procedure Terminate;

    Constructor Create(     OnRec  : T_ReceiveEvent;
                            Sock   : TSocket       ;
                        var Buffer : AnsiString    ;
                            Ind    : Integer         );

  end;

implementation

{ ClntListen }

// ################################################################# Constructor

Constructor T_ClntListen.Create(     OnRec  : T_ReceiveEvent;
                                     Sock   : TSocket       ;
                                 var Buffer : AnsiString    ;
                                     Ind    : Integer         );
begin
  oSock      := Sock  ;
  oOnReceive := OnRec ;
  oBuffer    := Buffer;
  oIndex     := Ind   ;
  inherited create( false ) ;
  Priority        := tpLower;
  FreeOnTerminate := true   ;
end;

// ################################################################# Constructor
// ################################################################# Synchronize

procedure T_ClntListen.Synchronize( Buffer : AnsiString; Res : Integer );
begin
  if Res > 0 then
  begin
    SetLength( Buffer, Res );
    if Assigned( oOnReceive ) then oOnReceive( Res, oIndex, Buffer );
  end
  else
  begin
    if Assigned( oOnReceive ) then oOnReceive( Res, oIndex, 'Clnt-Fehler: ' + IntToStr( WSAGetLastError ) );
  end;
end;

// ################################################################# Synchronize
// ##################################################################### Execute

procedure T_ClntListen.Execute;
var
  Res : Integer;

begin
  repeat
    Res := recv( oSock, oBuffer[1], BuffLen, 0  );
    Synchronize( oBuffer, Res );
  until Terminated;
end;

// ##################################################################### Execute

procedure T_ClntListen.Terminate;
begin
  inherited Terminate;
end;

end.

Dieser Faden wird beendet, wenn die Verbindung getrennt wird. Ich hoffe, jemand kann mir sagen, ob das "Sicher" ist und wenn nicht, was ich ändern muß.

Sollte das nicht reichen, kann ich auch das ganze Test-Projekt hochladen.

Grüße von der sonnigen Nordsee

Peter
jfheins
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 918
Erhaltene Danke: 158

Win 10
VS 2013, VS2015
BeitragVerfasst: Mi 22.04.15 20:49 
Also an den Threrads gibt es jetzt nicht soo viel auszusetzen. Nur die Methode Synchronize, die du deklariert hast, verwirrt ein wenig, da sie eben nicht mit dem Hauptthread synchronisiert.

Also der Code, den du gezeigt hast, müsste gehen. Falls allerdings in den ganzen Events (oOnAccept, oOnReceive) irgendwas gemacht wird, was auf die GUI zugreift, ist das böse. Denn diese Events laufen ja immer noch im erstellten Thread.

Gerade die Formulierung "trägt Socket und Buffer in einem Array ein" lässt die Spekulation zu, dass Daten geschrieben werden. Falls dort keine Synchronisierung (lock?) stattfindet ist das eher nicht threadsicher.

Falls du dich jetzt fragst, wie man denn Sachen (wie die Eventhandler) eigentlich synchonisiert, sodass sie im Hauptthread ablaufen... So ungefähr:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TClntListen.TriggerOnReceive(); // Methode OHNE Parameter!
begin  
    if Assigned( oOnReceive ) then
        oOnReceive( FRes , FIndex, FBuffer ); // Diese Parameter müssten dann noch zu Feldern der Klasse werden!
end;

procedure TClntListen.Execute;
begin
  oSock := accept( oSSock, NilNil );
  FRes := 123// Felder befüllen
  FIndex = 456;
  FBuffer := 'lalala';
  Synchronize(TriggerOnReceive); // Man übergibt der Methode Synchronize die Methode die ausgeführt werden soll
end;
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Do 23.04.15 10:58 
Hallo jfheins,

danke für Deine Antwort. Sieht so aus, als ob sie mich ein Stück weiter bringt. Zu Deiner Spekulation: Der Thread mit der Funktion "accept" wird terminiert bevor "oSock" in das Array eingetragen wird. Ist es dennoch notwendig, diesen Wert im Tread zu kopieren?
ausblenden Delphi-Quelltext
1:
2:
3:
4:
  if Sock <> INVALID_SOCKET then
  begin
    oAcceptTh.Terminate;
    oSock[ Ind ].Sock := Sock;

Ich werde die Treads so umbauen, wie ich Deine Antwort verstanden habe und dann berichten.

Grüße von der wolkigen Nordsee

Peter
Nersgatt
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 1581
Erhaltene Danke: 279


Delphi 10 Seattle Prof.
BeitragVerfasst: Do 23.04.15 11:08 
Mit dem Aufruf von .Terminate ist der Thread aber noch nicht beendet. Er ist nur aufgefordert worden, sich zu beenden.
Schau mal in den Quelltext von TThread.Terminate rein. Dort siehst Du, dass dort eigentlich nur die Variable Terminated auf True gesetzt wird.

Wenn nun Deine Execute-Procedure im Thread prinzipiell so aussieht:

ausblenden Delphi-Quelltext
1:
2:
while not Terminated do
  MachIrgendwas;


wirst Du sehen, dass Dein Thread erst dann Terminiert, wenn "MachIrgendwas" mit seiner Arbeit fertig ist, und der Schleifenkopf das nächste mal auf Terminated prüft.

_________________
Gruß, Jens
Zuerst ignorieren sie dich, dann lachen sie über dich, dann bekämpfen sie dich und dann gewinnst du. (Mahatma Gandhi)
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Do 23.04.15 12:01 
Hallo Jens,

auch Dir Dank für Deine Antwort. Es ist ja gerade mein Problem, dass diese Quelltexte bei meinem Delphi nicht vorhanden sind. Was kann ich tun, damit es sauber abläuft??

Ich habe die Threads jetzt umgebaut, aber nach abbrechen der Verbindung zum Server ist ein erneutes Verbinden nicht möglich. Es muß also im Zusammenhang mit dem "accept-Tread" noch etwas falsch laufen.

Accept:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
unit TcpSrvAccept;

interface

uses
  Classes, Winsock,
  {eigene}
  TCP_IP, WinSock2;

type
  T_TcpSrvAccept = class(TThread)
  private
    { Private-Deklarationen}
    oSSock     : TSocket       ;
    oSock      : TSocket       ;
    oClntSock  : TSocket       ;
    oOnAccept  : T_AcceptEvent ;
    oInd       : Integer       ;

  protected
    procedure Execute; override;
    procedure TriggerOnReceive;

  public
    Constructor Create(     OnAcc : T_AcceptEvent;
                        var Sock  : TSocket      ;
                            Ind   : Integer        );
    procedure Terminate;

  end;

implementation

// ################################################################# Constructor

Constructor T_TcpSrvAccept.Create(     OnAcc : T_AcceptEvent;
                                   var Sock  : TSocket      ;
                                       Ind   : Integer        );
begin
  oOnAccept  := OnAcc         ;
  oSSock     := Sock          ;
  oSock      := INVALID_SOCKET;
  oClntSock  := INVALID_SOCKET;
  oInd       := Ind           ;
  inherited create( false )   ;
  Priority        := tpLower  ;
  FreeOnTerminate := true     ;
end;

// ################################################################# Constructor
// ################################################################# Synchronize

procedure T_TcpSrvAccept.TriggerOnReceive;
begin
  if Assigned( oOnAccept ) then oOnAccept( oClntSock, oInd );
end;

// ################################################################# Synchronize
// ##################################################################### Execute

procedure T_TcpSrvAccept.Execute;
begin
  oSock     := accept( oSSock, NilNil );
  oClntSock := oSock;
  Synchronize( TriggerOnReceive );
end;

// ##################################################################### Execute
// ################################################################### Terminate

procedure T_TcpSrvAccept.Terminate;
begin
  inherited Terminate;
end;

// ################################################################### Terminate

end.


Listen:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
unit Clnt_Listen;

interface

uses
  SysUtils, Classes, winsock,
  { eigene }
  TCP_IP;

type
  T_ClntListen = class(TThread)
  private
    { Private-Deklarationen}
    oSock      : TSocket       ;
    oOnReceive : T_ReceiveEvent;
    oBuffer    : AnsiString    ;
    oMsg       : AnsiString    ;
    oIndex     : Integer       ;
    oRes       : Integer       ;

  protected
    procedure Execute;                                                 override;
    procedure TriggerOnReceive;

  public
    procedure Terminate;

    Constructor Create(     OnRec  : T_ReceiveEvent;
                            Sock   : TSocket       ;
                        var Buffer : AnsiString    ;
                            Ind    : Integer         );

  end;

implementation

{ ClntListen }

// ################################################################# Constructor

Constructor T_ClntListen.Create(     OnRec  : T_ReceiveEvent;
                                     Sock   : TSocket       ;
                                 var Buffer : AnsiString    ;
                                     Ind    : Integer         );
begin
  oSock      := Sock  ;
  oOnReceive := OnRec ;
  oBuffer    := Buffer;
  oMsg       := ''    ;
  oIndex     := Ind   ;
  inherited create( false ) ;
  Priority        := tpLower;
  FreeOnTerminate := true   ;
end;

// ################################################################# Constructor
// ################################################################# Synchronize

procedure T_ClntListen.TriggerOnReceive;
begin
  if Assigned( oOnReceive ) then oOnReceive( oRes, oIndex, oMsg );
end;

// ################################################################# Synchronize
// ##################################################################### Execute

procedure T_ClntListen.Execute;
var
  Res : Integer;

begin
  repeat
    SetLength( oBuffer, BuffLen );
    Res := recv( oSock, oBuffer[1], BuffLen, 0  );
    if Res >= 0 then
    begin
      SetLength( oBuffer, Res );
      oMsg := oBuffer          ;
    end
    else oMsg := 'Clnt-Fehler: ' + IntToStr( WSAGetLastError );
    oRes := Res;
    Synchronize( TriggerOnReceive );
  until Terminated;
end;

// ##################################################################### Execute

procedure T_ClntListen.Terminate;
begin
  inherited Terminate;
end;

end


Die Ereignisroutine "iOnAccept" speichert den Client-Socket in einem Array und startet einen Listen-Thread sowie einen neuen Accept-Tread für weitere Clients. Außerdem wird eine externe Ereignisroutine aufgerufen. Wenn ich das richtig verstanden habe muß der Tread aber beendet sein, wenn ich den Client-Socket speichere, aber das geschieht ja erst mit dem Beenden der Ereignisroutine. Was kann ich also tun, um diese Nuß zu knacken??? Einen weiteren Thread??

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure T_TcpServer.iOnAccept( Sock : TSocket; Ind : Integer );
var
  I : Integer;

begin
  if Sock <> INVALID_SOCKET then
  begin
    oAcceptTh.Terminate;
    oSock[ Ind ].Sock := Sock ;


Grüße von der wieder sonnigen Nordsee

Peter
baumina
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 305
Erhaltene Danke: 61

Win 7
Delphi 10.2 Tokyo Enterprise
BeitragVerfasst: Do 23.04.15 13:45 
Der accept-Thread hat keine Schleife (while not Terminated), deswegen läuft der genau einmal und ist dann fertig.
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Do 23.04.15 16:25 
Hallo baumina,

Dank für Deine Antwort! "oAcceptTh.Terminate;" Habe ich auch aus lauter Verzweifelung hineingeschrieben, aber ich kann einen Client nicht mehr verbinden, wenn ein Abbruch der Verbindung stattgefunden hat. Seltsamer Weise ging das mal alles.

Grüße von der noch immer sonnigen Nordsee

Peter
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Fr 24.04.15 11:02 
Ein freundliches Hallo an alle,

nach wie vor ist das Verhalten meiner Fäden etwas seltsam. Ich habe den Listen-Thread umgebaut, damit ich ihn leicht terminieren kann. mit "Res := recv( oSock, oBuffer[1], BuffLen, MSG_PEEK );" sollte die Funktion Prüfen, ob etwas empfangen wurde und sofort mit der Anzahl Zeichen zurückkehren. Erst wenn die Anzahl der Zeichen > 0 ist soll der Puffer gelesen werden. Damit soll die Schleife ständig durchlaufen werden und damit auch beendet werden können. Doch Banane!

"Res := recv( oSock, oBuffer[1], BuffLen, MSG_PEEK );" verhält sich wie "Res := recv( oSock, oBuffer[1], BuffLen, 0 );"!

Es muß also noch etwas Faul sein! Hier der aktuelle Stand: (Der Rest ist wie oben)
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
procedure T_ClntListen.Execute;
var
  Res : Integer;

begin
  repeat
    SetLength( oBuffer, BuffLen );
    Res := recv( oSock, oBuffer[1], BuffLen, MSG_PEEK );
    if Res = 0
    then Application.ProcessMessages  
    else
    begin
      Res := recv( oSock, oBuffer[1], BuffLen, 0  );
      if Res >= 0 then
      begin
        SetLength( oBuffer, Res );
        oMsg := oBuffer          ;
      end
      else oMsg := 'Clnt-Fehler: ' + IntToStr( WSAGetLastError );
      oRes := Res;
      Synchronize( TriggerOnReceive );
    end;
  until Terminated;
end;

So hatte ich mir das gedacht:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure T_TcpClient.Disconnect;
begin
  oListen.Terminate;
  oListen.WaitFor  ;
  oCloseSocket( nil, oSock, true );
  oConn := false;
end;


Grüße von der sonnigen Nordsee mit leichten Schleierwolken

Peter
Sinspin
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1321
Erhaltene Danke: 117

Win 10
RIO, CE, Lazarus
BeitragVerfasst: Fr 24.04.15 13:14 
Hallo Peter,

warum überschreibst Du Terminate? Du veränderst dessen Verhalten ja nicht in der Ableitung. Zumal die Ableitung nicht vollständigt ist. In der Deklaration fehlt ein override.
Thread Prioriät sollte man nie verändern. Wen man nicht will das ein Thread permanent CPU verbraucht kann man ihn auf ein Ereignis warten lassen oder immer wieder schlafen schicken via Sleep.
Niemals Application.ProcessMessages in einem Thread verwenden! Das gehört da einfach nicht hin.

Threads Debuggen ist nicht ganz lustig. Aber Du könntest via Windows.OutputDebugString Informationen nach außen liefern. Die werden Dir in Delphi unter Ansicht/Debug-Fenster/Ereignisprotokoll angezeigt. So kannst Du verfolgen was wann passiert. Hoffe das es das in D4 schon gibt.
Wenn irgendwie möglich solltest Du versuchen eine neuere Delphi version aufzutreiben ;-)

Grüße von der arabischen Halbinsel.

_________________
Wir zerstören die Natur und Wälder der Erde. Wir töten wilde Tiere für Trophäen. Wir produzieren Lebewesen als Massenware um sie nach wenigen Monaten zu töten. Warum sollte unser aller Mutter, die Natur, nicht die gleichen Rechte haben?
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Fr 24.04.15 14:25 
Hallo Stefan,

Dank Dir für Deine Antwort. Einen Fehler habe ich inzwischen gefunden, doch der hatte nichts mit den Thread zu tun. Sleep hatte ich probiert, "Application.ProcessMessages" war eine Verzweiflungstat. Inzwischen habe ich wieder einiges zurückgebaut. "Ansicht/Debug-Fenster/Ereignisprotokoll" gibt es bei meinem Delphi leider nicht. Aber ich kenne jetzt ein Problem.

"recv" gibt die Kontrolle erst zurück, wenn etwas empfangen wurde, auch wenn es ein Abbruch war. Ich hatte erst "oListen.Terminate;" dann "oCloseSocket( nil, oSock, true );" aufgerufen. Umgekehrt funktioniert es dann.

Nun muß ich noch einen Fehler auf der Serverseite finden und kann dann hoffentlich von Erfolg berichten.

Grüße auf die arabische Halbinsel von der noch sonnigen Nordsee

Peter
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Sa 25.04.15 12:29 
Hallo Stefan,

Ich vergaß: "Terminate" stammt noch aus dem ersten Ansatz, bei dem ich dachte hier müsse eventuell noch etwas getan werden. Solche Dinge bereinige ich in der Regel, wenn es erst einmal grob läuft.

Es gibt aber noch ein Problem: Wenn der Client die Verbindung abbricht wird der Server-Socket ungültig. Ein wartender Accept kehrt mit -1 zurück. Ein neuer Listen am Serversocket erhält ebenfals -1 und damit ist kein Verbinden mehr möglich. Der Client ruft "closesocket( Sock );" auf, dann "WSACleanup;", danach "oListen.Terminate;". Aber auch ein Aufruf von "oListen.Terminate;" vor "WSACleanup;" ändert nichts. Irgend etwas ist hier noch faul.

Grüße von der nieselnden Nordsee

Peter
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Mo 27.04.15 11:11 
Ein freundliches Hallo an alle,

nach diversen Versuchen verhält sich das ganze ziemlich stabil, aber nicht so wie es soll. Der Datenaustausch über den Listen-Thread funktioniert sicher. Wenn aber der Client die Verbindung abbricht, wird auch der Serversocket abgeschossen. Die Frage ist nun: liegt es am Thread oder am Socket? Wird der Serversocket richtig ubergeben??

Hier der aktuelle "Execute" des Listen-Faden:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
procedure T_ClntListen.Execute;
var
  Res  : Integer;
  Buff : AnsiString;

begin
  repeat
    SetLength( Buff, BuffLen );
    Res  := recv( oSock, Buff[1], BuffLen, 0 );
    oErr := WSAGetLastError;
    oRes := Res;
    if Res > 0 then
    begin
      SetLength( Buff, Res );
      oMsg := Buff;
    end
    else
    begin
      oMsg := 'Clnt-Fehler: ' + IntToStr( oErr );
      Terminate;
      Res  := shutdown( oSock, SD_BOTH );
      if Res = 0 then
      begin
        oMsg := oMsg + ' ' + IntToStr( Res );
        Res  := closesocket( oSock );
        if Res = 0 then
        begin
          oMsg := oMsg + ' ' + IntToStr( Res );
          WSACleanup;
        end;
      end;
    end;
    Synchronize( TriggerOnReceive );
    SetLength( Buff, BuffLen )  ;
  until Terminated;
end;

Wenn der Client die Verbindung abbricht, kehrt auch "accept" zurück und im folgenden schlagen alle Verbindungsversuche des Client fehl. Der "accept - Thread" kann nicht aufgebaut werden weil zuvor Listen fehlschlägt, aber auch ohne Listen geht es dann nicht. Wird der "Serversocket" neu aufgebaut, so werden alle wartende Verbindungsversuche akzeptiert! (Auch mehrere Versuche eines Client vor dem Neuaufbau des Serversocket.)
Warscheinlich ist der Fehler so einfach, dass ich ihn nicht finde.

Hier der "accept - tread":
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
unit TcpSrvAccept;

interface

uses
  Classes, Winsock,
  {eigene}
  TCP_IP, WinSock2;

type
  T_TcpSrvAccept = class(TThread)
  private
    { Private-Deklarationen}
    oSSock     : TSocket       ;   // Server Socket
    oClntSock  : TSocket       ;   // Client Socket
    oOnAccept  : T_AcceptEvent ;
    oInd       : Integer       ;

  protected
    procedure Execute; override;
    procedure TriggerOnReceive;

  public
    Constructor Create(     OnAcc : T_AcceptEvent;
                        var Sock  : TSocket      ;
                            Ind   : Integer        );

  end;

implementation

// ################################################################# Constructor

Constructor T_TcpSrvAccept.Create(     OnAcc : T_AcceptEvent;
                                   var Sock  : TSocket      ;
                                       Ind   : Integer        );
begin
  oOnAccept  := OnAcc         ;
  oSSock     := Sock          ;
  oClntSock  := INVALID_SOCKET;
  oInd       := Ind           ;
  inherited create( false )   ;
  Priority        := tpLower  ;
  FreeOnTerminate := true     ;
end;

// ################################################################# Constructor
// ################################################################# Synchronize

procedure T_TcpSrvAccept.TriggerOnReceive;
begin
  if Assigned( oOnAccept ) then oOnAccept( oClntSock, oInd );
end;

// ################################################################# Synchronize
// ##################################################################### Execute

procedure T_TcpSrvAccept.Execute;
begin
  oClntSock := accept( oSSock, NilNil );
  Synchronize( TriggerOnReceive );
end;

// ##################################################################### Execute

end.

Den Server - Socket habe ich als "var" übergeben, weiß aber nicht, ob das notwendig ist. Das Verhalten ändert sich nicht.

Der Aufbau des Serversocket:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
// Server ############################################################## Private

procedure T_TcpServer.ConnectNext( I : Integer );
var
  Res : Integer;

begin
  if oDebug then oDebugInf.Lines.Add( 'listen Client: ' + IntToStr( I ) );
  Res := listen( oSSock, 10 );    //      SOMAXCONN
  if oDebug then oDebugInf.Lines.Add( 'listen: ' + IntToStr( Res ) + CrLf + IntToStr( WSAGetLastError ) );
  if Res = 0 then
  begin
    if oDebug then oDebugInf.Lines.Add( 'accept: ' + IntToStr( I ) );
    // Start thread:  Accept a client socket
    oWait     := true;
    oAcceptTh := T_TcpSrvAccept.Create( iOnAccept, oSSock, I );
  end;
end;

// Server ############################################################## Private
//                                               oStartup ( WSAStartup )

function T_TcpServer.oStartup : Integer;
var
  VerR    : word;
  WSADATA : TWSAData;
  Res     : Integer;

begin
  if oDebug then oDebugInf.Lines.Add( 'WSAStartup' );
  oSSock := INVALID_SOCKET;
  VerR   := 2;
  Res    := WSAStartup( VerR, WSADATA );
  if oDebug then oDebugInf.Lines.Add( 'WSAStartup: ' + IntToStr( Res ) );
  if Res <> 0 then
  begin
    if oDebug then oDebugInf.Lines.Add( 'Fehler bei WSAStartup: ' + IntToStr( Res ) );
    SetError( 'Fehler beim Initialisieren der Netzwerkverbindung: ' + GetWSAError( Res ) );
  end;
  Result := Res;
end;


// Server ############################################################## Private
//                                          ogetaddrinfo ( getaddrinfo )

function T_TcpServer.ogetaddrinfo( var ARes : PAddrInfo ) : Integer;
var
  hints : PAddrInfo;
  Res   : Integer;

begin
  if oDebug then oDebugInf.Lines.Add( 'getaddrinfo' );
  hints             := AllocMem( SizeOf( TAddrInfo ) );
  hints.ai_family   := AF_INET;
  hints.ai_socktype := SOCK_STREAM;
  hints.ai_protocol := IPPROTO_TCP;
  hints.ai_flags    := AI_PASSIVE;
  // Resolve the server address and port
  Res := getaddrinfo( nil, PChar( oPort ), hints, ARes );
  if oDebug then oDebugInf.Lines.Add( 'getaddrinfo: ' + IntToStr( Res ) );
  if Res <> 0 then
  begin
    if oDebug then oDebugInf.Lines.Add( 'Fehler bei getaddrinfo: ' + IntToStr( Res ) );
    SetError( 'Fehler bei der auflösung des Servernamens oder Ports: ' + CrLf + GetWSAError( Res ) );
    oCloseSocket( nil, INVALID_SOCKET, true );   // freeaddrinfo, closesocket, WSACleanup
  end;
  FreeMem( hints );
  Result := Res;
end;

// Server ############################################################## Private
//                                                 oGetsocket ( socket )

function T_TcpServer.oGetsocket( ARes : PAddrInfo ): Integer;
begin
  if oDebug then oDebugInf.Lines.Add( 'socket' );
  oSSock := socket( ARes.ai_family, ARes.ai_socktype, ARes.ai_protocol );
  if oDebug then oDebugInf.Lines.Add( 'socket: ' + IntToStr( oSSock ) );
  if oSSock = INVALID_SOCKET then
  begin
    if oDebug then oDebugInf.Lines.Add( 'Fehler bei socket: ' + IntToStr( WSAGetLastError ) );
    SetError( 'Fehler beim Einrichten des Ports: ' + CrLf + GetWSAError( oSSock ) );
    oCloseSocket( ARes, INVALID_SOCKET, true );  // freeaddrinfo, closesocket, WSACleanup
    Result := -1;
  end
  else Result := 0;
end;

// Server ############################################################## Private
//                                            oSetPortOpt ( setsockopt )

function T_TcpServer.oSetPortOpt( ARes : PAddrInfo ): Integer;
var
  Res : Integer;

begin
  if oDebug then oDebugInf.Lines.Add( 'setsockopt' );
  Res := setsockopt( oSSock, SOL_SOCKET, SO_REUSEADDR, 'true'4 );
  if oDebug then oDebugInf.Lines.Add( 'setsockopt: ' + IntToStr( Res ) );
  if Res <> 0 then
  begin
    if oDebug then oDebugInf.Lines.Add( 'Fehler bei setsockopt: ' + IntToStr( Res ) );
    SetError( 'Fehler beim Setzen der Port - Optionen: ' + CrLf + GetWSAError( Res ) );
    oCloseSocket( ARes, oSSock, true );          // freeaddrinfo, closesocket, WSACleanup
  end;
  Result := Res;
end;

// Server ############################################################## Private
//                                                        obind ( bind )

function T_TcpServer.obind( ARes : PAddrInfo ): Integer;
var
  Res : Integer;

begin
  if oDebug then oDebugInf.Lines.Add( 'bind' );
  Res := bind( oSSock, ARes.ai_addr^, ARes.ai_addrlen );
  if oDebug then oDebugInf.Lines.Add( 'bind: ' + IntToStr( Res ) );
  if Res = SOCKET_ERROR then
  begin
    if oDebug then oDebugInf.Lines.Add( 'Fehler beim binden: ' + IntToStr( Res ) );
    SetError( 'Fehler beim Verbinden mit dem Port: ' + CrLf + GetWSAError( Res ) );
    oCloseSocket( ARes, oSSock, true );          // freeaddrinfo, closesocket, WSACleanup
  end;
  Result := Res;
end;

// Server ############################################################## Private
//                                               olistenSSock ( listen )

function T_TcpServer.olistenSSock( ARes : PAddrInfo ): Integer;
var
  Res : Integer;

begin
  freeaddrinfo( ARes );
  if oDebug then oDebugInf.Lines.Add( 'listen' );
  Res := listen( oSSock, 10 );     //  SOMAXCONN  
  if oDebug then oDebugInf.Lines.Add( 'listen: ' + IntToStr( Res ) );
  if Res <> 0 then
  begin
    if oDebug then oDebugInf.Lines.Add( 'Fehler beim listen: ' + IntToStr( Res ) );
    SetError( 'Fehler beim listen am Server-Socket: ' + CrLf + GetWSAError( Res ) );
    oCloseSocket( nil, oSSock, true );          // freeaddrinfo, closesocket, WSACleanup
  end;
  Result := Res;
end;

// Server ############################################################## Private
//                                                    oaccept ( accept )

procedure T_TcpServer.oaccept( I : Integer );
begin
  if oDebug then oDebugInf.Lines.Add( 'accept' );
  oWait     := true;
  oAcceptTh := T_TcpSrvAccept.Create( iOnAccept, oSSock, I );
end;

// Server ############################################################## Private
//                                                           PrepConnect

procedure T_TcpServer.PrepConnect( I : Integer );   //  2
var
  ARes : PAddrInfo;
  Res  : Integer;

begin
  Res := oStartup;                               // Initialize Winsock
  if Res = 0 then Res := ogetaddrinfo( ARes );   // Resolve the server address and port
  if Res = 0 then Res := oGetsocket  ( ARes );   // Create a SOCKET for connecting to server
  if Res = 0 then Res := oSetPortOpt ( ARes );   // Port auf SO_REUSEADDR
  if Res = 0 then Res := obind       ( ARes );   // Setup the TCP listening socket
  if Res = 0 then Res := olistenSSock( ARes );
  if Res = 0 then oaccept( I );                  // Accept a client socket
end;

// Server ############################################################## Private

"ConnectNext" startet einen neuen "accept-thread", wenn eine Verbindung aufgebaut wurde. 10 Vebindungen werden angenommen. wenn eine Verbindung abgebrochen wird, kann eine Neue aufgebaut werden. (So soll es sein.) Bisher werden alle Verbindungen abgebrochen, wenn ein Client sich "Abmeldet".

Grüße von der heute wieder sonnigen Nordsee

Peter
Sinspin
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1321
Erhaltene Danke: 117

Win 10
RIO, CE, Lazarus
BeitragVerfasst: Mo 27.04.15 18:13 
Hallo Peter,

kannst Du bitte mal dein gesamtes Testprojekt anhängen? Mit den ausschnitten tue ich mich leider recht schwehr durchzusteigen woher die Probleme kommen könnten.

_________________
Wir zerstören die Natur und Wälder der Erde. Wir töten wilde Tiere für Trophäen. Wir produzieren Lebewesen als Massenware um sie nach wenigen Monaten zu töten. Warum sollte unser aller Mutter, die Natur, nicht die gleichen Rechte haben?

Für diesen Beitrag haben gedankt: Peter18
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Di 28.04.15 10:23 
Hallo Stefan,

Dank Dir, Sende ich Dir zu.

Grüße von der sonnigen Nordsee

Peter
Peter18 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 489
Erhaltene Danke: 2


Delphi4
BeitragVerfasst: Mo 04.05.15 15:12 
Ein freundliches Hallo an alle,

kleine Ursache, große Wirkung. Die Probleme, die ich zum Schluß hatte, hatten nichts mit den Threads oder mit Socketeinstellungen zu tun. Wenn man bei größeren Änderungen zwischen durch vom Telefon belästigt wird, kann man schon mal etwas als erledigt abhacken, was aber doch noch nicht fertig ist.

So war es auch hier. Der Client sollte eine Verbindung aufbauen, wenn Server und Port eingetragen sind. Durch den Anruf fehlte die Prüfung, ob bereits eine Verbindung besteht. Dadurch hat der Client 2 Verbindungen aufgebaut, was zu "lustigen" Nebeneffekten führte. Die haben mich in die Irre geführt und ich habe an der falschen Stelle gesucht.

Grüße von der sonnigen Nordsee

Peter