![]() |
Socket-Verbindung herstellen ohne zu blockieren
Moin!
Ich muss gerade eine Legacy-Anwendung von uns etwas ändern. Diese kommuniziert via TClientSocket mit einem Server. Da es bei dem Kunden gelegentlich zu Verbindungsstörungen kommt, und sich auch nach längerer Suche für die Ursache keine "richtige" Abhilfe abzeichnet, man aber dennoch weiter arbeiten möchte bis das mal so weit ist, soll mein Programm nun immer mal wieder versuchen sich neu zu verbinden wenn die Verbindung abgebrochen ist. Dafür habe ich mal spontan einen Timer genommen, der einfach alle 10s prüft ob .Connected = true, und wenn nicht dies setzen. Leider aber friert während des Verbindungsversuchs alles ein, so dass das Programm bei fortwährender Störung alle 10s für 2-3s nicht bedient werden kann. (Man kann sinnvolle Dinge auch ohne die Verbindung tun.) Kann man den Socket dazu überreden, dies bitteschön im Hintergrund zu tun? Die Events sind alle versorgt, und anhand derer setze ich auch entsprechende Flags. Sodass mir der "Zwischenzustand" nichts ausmachen würde. Ich habe schon lose über einen Thread dafür nachgedacht, aber wenn ich mich nicht täusche kann man Sockets nur in dem jeweiligen Kontext nutzen, in dem sie erstellt bzw. verbunden werden. Und ich habe weder Lust noch Zeit das gesamte Programm so umzustellen, dass ALLE Socket-Zugriffe in einem Thread passieren (mit all dem Signaling usw. was da dran hängen würde). Gibt's da einen Kniff? |
AW: Socket-Verbindung herstellen ohne zu blockieren
Nö, was sinnvolles weiß ich nicht, aber wenn der Fehler auftritt, dann in einem Thread alle 10s (oder so) prüfen, ob die Verbindung prinzipiell wiederhergestellt werden kann. Wenn ja, dann im eigentlichen Programmteil die Verbindung wieder aufbauen.
|
AW: Socket-Verbindung herstellen ohne zu blockieren
Socketzugriffe auf die native Windows Socket API können durchaus auch aus mehreren Threads stattfinden (Beispiel: Indy's Telnet Client schreibt auf den Socket aus einem Thread während ein anderer daraus liest). Bei TClientSocket ist aber leider die gesamte Verarbeitung zusätzlich noch an die Windows Ereignis/Nachrichtenwarteschlange gekoppelt um so die "asynchrone" Verarbeitung zu ermöglichen (Relikt aus den frühen Windows-Zeiten), mit Windowhandles usw. und Dadurch wird das Thema "welcher Thread darf was?" dann doch wieder relevant.
Durch TClientSocket entsteht in der Regel komplexer ("Spaghetti-")Code, der bei einem blockierenden Zugriff auf Sockets wegfällt, was die Wartung langfristig wieder einfacher macht. |
AW: Socket-Verbindung herstellen ohne zu blockieren
Hi, ich hab hier zwei Sources entdeckt die das Thema Socket+(multi)Thread aufgreifen, vielleicht ist etwas für Dich dabei?
![]() ![]() Mein Tipp wäre erstmal anpingen ob Server überhaupt existiert um Connected = true zu setzen. edit für alle dir nur source wollen, hier der aus link 1 Es kann gut möglich sein das in den Links noch Verbesserungen o.ä. erwähnt werden.
Delphi-Quellcode:
hier der aus link 2
unit Server;
{$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Sockets, ServerThread; type { TForm1 } TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); public procedure handle(ID, message: String); end; var Form1: TForm1; threads: Array[1..4] of TServerThread; serverSocket: Longint; clientSocket: Longint; serverAddr: TInetSockAddr; opt: Integer = 1; addrSize: Longint; clientCount: Integer = 0; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); begin repeat serverSocket:= fpSocket(AF_INET, SOCK_STREAM, 0); if fpSetSockOpt(serverSocket, SOL_SOCKET, SO_REUSEADDR, @opt, sizeOf(opt)) = SOCKET_ERROR then showMessage('Server : Multi : ' + intToStr(socketError)); if serverSocket = SOCKET_ERROR then showMessage('Server : Socket : ' + intToStr(socketError)); serverAddr.sin_family:= AF_INET; serverAddr.sin_port:= htons(50000); serverAddr.sin_addr.s_addr:= htonl($7F000001); if fpBind(serverSocket, @serverAddr, sizeOf(serverAddr)) = SOCKET_ERROR then showMessage('Server : Bind : ' + intToStr(socketError)); if fpListen(serverSocket, 4) = SOCKET_ERROR then showMessage('Server : Listen : ' + intToStr(socketError)); showMessage('Waiting for connect from Client...'); addrSize:= sizeOf(serverAddr); clientSocket:= fpaccept(serverSocket, @serverAddr, @addrSize); if clientSocket = SOCKET_ERROR then showMessage('Server : Accept : ' + intToStr(socketError)) else clientCount:= clientCount + 1; threads[clientCount]:= TServerThread.create(true, clientSocket); threads[clientCount].start; until clientCount = 4; end; procedure TForm1.handle(ID, message: String); var i, toTerminate: Integer; MyCriticalSection: TRTLCriticalSection; begin InitCriticalSection(MyCriticalSection); EnterCriticalSection(MyCriticalSection); try for i:= 1 to clientCount do begin threads.send(ID + ': ' + message); if threads.getID = ID then toTerminate:= i; end; if message = 'ciao' then begin threads[toTerminate].send('ciao'); threads[toTerminate].close; clientCount:= clientCount - 1; for i:= toTerminate to clientCount do threads:= threads[i + 1]; end; finally LeaveCriticalSection(MyCriticalSection); end; end; end. unit ServerThread; {$mode objfpc}{$H+} interface uses Classes, Dialogs, Sockets, SysUtils; type TServerThread = class(TThread) private ID: String; clientSocket: Longint; protected procedure execute; override; public constructor create(createSuspended: Boolean; client: Longint); procedure send(msg: String); function getID: String; procedure close; end; var buffer: String[255]; count: Longint; implementation uses Server; constructor TServerThread.create(createSuspended: Boolean; client: Longint); begin freeOnTerminate:= true; inherited create(createSuspended); clientSocket:= client; end; procedure TServerThread.execute; begin count:= fprecv(clientSocket, @buffer[1], 255, 0); if (count <> SOCKET_ERROR) and (count > 0) then begin setLength(buffer, count); ID:= buffer; end; buffer:= 'Herzlich willkommen im Chat, ' + ID; count:= length(buffer); if fpsend(clientSocket, @buffer[1], count, 0) = count then begin repeat count:= fprecv(clientSocket, @buffer[1], 255, 0); if (count <> SOCKET_ERROR) and (count > 0) then begin setLength(buffer, count); Form1.handle(ID, buffer); end; until (count = SOCKET_ERROR) or (count = 0); end; end; procedure TServerThread.send(msg: String); begin fpsend(clientSocket, @msg[1], length(msg), 0); end; function TServerThread.getID: String; begin result:= ID; end; procedure TServerThread.close; begin closeSocket(clientSocket); end; end. unit Client; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Sockets, ClientThread; type { TForm1 } TForm1 = class(TForm) Button1: TButton; Button2: TButton; Edit1: TEdit; Edit2: TEdit; Memo1: TMemo; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Edit1Change(Sender: TObject); procedure handle(msg: String); private { private declarations } public { public declarations } end; var Form1: TForm1; thread: TClientThread; serverAddr: TInetSockAddr; serverSocket: Longint; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); begin serverSocket:= fpSocket(AF_INET, SOCK_STREAM, 0); if serverSocket = SOCKET_ERROR then showMessage('Client : Socket : ' + intToStr(socketError)); serverAddr.sin_family:= AF_INET; serverAddr.sin_port:= htons(50000); serverAddr.sin_addr.s_addr:= htonl($7F000001); //funktioniert beim zweiten Client nicht, da kein Error, obwohl die Verbindung nicht zustande kommt (fpaccept reagiert nicht) if fpconnect(serverSocket, @serverAddr, sizeOf(serverAddr)) = SOCKET_ERROR then showMessage('Client : Connect : ' + intToStr(socketError)); thread:= TClientThread.create(true, serverSocket); thread.start; buffer:= Edit1.Text; fpsend(serverSocket, @buffer[1], length(buffer), 0); Button2.Enabled:= true; end; procedure TForm1.Button2Click(Sender: TObject); var buffer: String; begin buffer:= Edit2.Text; fpsend(serverSocket, @buffer[1], length(buffer), 0); end; procedure TForm1.Edit1Change(Sender: TObject); begin if length(Edit1.Text) > 0 then Button1.Enabled:= true else Button1.Enabled:= false; end; procedure TForm1.handle(msg: String); begin if msg = 'ciao' then closeSocket(serverSocket) else Memo1.Lines.Add(msg); end; end. unit ClientThread; {$mode objfpc}{$H+} interface uses Classes, Dialogs, Sockets, SysUtils; type TClientThread = class(TThread) private serverSocket: Longint; protected procedure execute; override; public constructor create(createSuspended: Boolean; server: Longint); end; var buffer: String[255]; count, i: Longint; implementation uses Client; constructor TClientThread.create(createSuspended: Boolean; server: Longint); begin freeOnTerminate:= true; inherited create(createSuspended); serverSocket:= server; end; procedure TClientThread.execute; begin repeat count:= fprecv(serverSocket, @buffer[1], 255, 0); if count <> SOCKET_ERROR then begin setLength(buffer, count); Form1.handle(buffer); end; until buffer = 'ciao'; closeSocket(serverSocket); end; end.
Delphi-Quellcode:
program server;
uses Forms, main in 'main.pas' {Form1}; {$R *.RES} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end. /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ Unit main; Interface Uses Windows, SysUtils, Messages, Classes, Forms, ScktComp, Controls, StdCtrls, Menus, Mask, Spin, ComCtrls, ExtCtrls; Const CM_IncCount = WM_USER + 1; Type TForm1 = Class(TForm) ServerSocket: TServerSocket; MainMenu: TMainMenu; File1: TMenuItem; ActiveItem: TMenuItem; N1: TMenuItem; Exit1: TMenuItem; Panel1: TPanel; Label1: TLabel; CacheEdit: TSpinEdit; Label2: TLabel; PortEdit: TSpinEdit; Label3: TLabel; ThreadCount: TEdit; Panel2: TPanel; ListBox1: TListBox; Panel3: TPanel; StatusBar1: TStatusBar; CharCount: TLabel; Procedure ServerSocketGetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; Var SocketThread: TServerClientThread); Procedure FormCreate(Sender: TObject); Procedure FormClose(Sender: TObject; Var Action: TCloseAction); Procedure Exit1Click(Sender: TObject); Procedure PortEditChange(Sender: TObject); Procedure ActiveItemClick(Sender: TObject); Procedure ServerSocketThreadEnd(Sender: TObject; Thread: TServerClientThread); Procedure ServerSocketThreadStart(Sender: TObject; Thread: TServerClientThread); Procedure CacheEditChange(Sender: TObject); protected Procedure CMIncCount(Var Msg: TMessage); message CM_IncCount; public End; { TFileServerThread } TFileServerThread = Class(TServerClientThread) public Procedure ClientExecute; override; End; Var Form1: TForm1; Implementation {$R *.DFM} { TFileServerThread } Procedure TFileServerThread.ClientExecute; Var Data: Array[0..1023] Of char; RecText: String; SocketStream: TWinSocketStream; Begin While Not Terminated And ClientSocket.Connected Do Try SocketStream := TWinSocketStream.Create(ClientSocket, 30000); Try FillChar(Data, SizeOf(Data), 0); If SocketStream.Read(Data, SizeOf(Data)) = 0 Then Begin // If we didn't get any data after xx seconds then close the connection ClientSocket.SendText('Timeout on Server'+#13#10); //Wait a little time to allow sending of text before disconnect sleep(1); ClientSocket.Close; Terminate; End; RecText := Data; If Length(RecText) > 2 Then Delete(RecText, Pos(#13#10, RecText), 2); // Delete 10 If ClientSocket.Connected Then Begin ClientSocket.SendText(RecText); SendMessage(Form1.Listbox1.Handle, LB_ADDSTRING, 0, Integer(PChar(RecText))); PostMessage(Form1.Handle, CM_INCCOUNT, 0, 0); End; Finally SocketStream.Free; End; Except HandleException; End; End; Procedure TForm1.ServerSocketGetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; Var SocketThread: TServerClientThread); Begin // Create a new thread for connection SocketThread := TFileServerThread.Create(False, ClientSocket); ClientSocket.SendText('Welcome to Server'+#13#10); End; Procedure TForm1.FormCreate(Sender: TObject); Begin CacheEdit.Value := ServerSocket.ThreadCacheSize; PortEdit.Value := ServerSocket.Port; CharCount.Caption := '0'; ActiveItemClick(Nil); End; Procedure TForm1.FormClose(Sender: TObject; Var Action: TCloseAction); Begin ServerSocket.Close; End; Procedure TForm1.CMIncCount(Var Msg: TMessage); Begin CharCount.Caption := IntToStr(StrToInt(CharCount.Caption) + 1); End; Procedure TForm1.Exit1Click(Sender: TObject); Begin Close; End; Procedure TForm1.PortEditChange(Sender: TObject); Begin ServerSocket.Port := StrToInt(PortEdit.Text); End; Procedure TForm1.ActiveItemClick(Sender: TObject); Begin ServerSocket.Active := Not ServerSocket.Active; ActiveItem.Checked := ServerSocket.Active; If ServerSocket.Active Then StatusBar1.SimpleText := 'Active' Else StatusBar1.SimpleText := 'InActive'; End; Procedure TForm1.ServerSocketThreadEnd(Sender: TObject; Thread: TServerClientThread); Begin ThreadCount.Text := IntToStr(StrToInt(ThreadCount.Text) - 1); End; Procedure TForm1.ServerSocketThreadStart(Sender: TObject; Thread: TServerClientThread); Begin ThreadCount.Text := IntToStr(StrToInt(ThreadCount.Text) + 1); End; Procedure TForm1.CacheEditChange(Sender: TObject); Begin ServerSocket.ThreadCacheSize := CacheEdit.Value; End; End. |
AW: Socket-Verbindung herstellen ohne zu blockieren
Die Idee die Verbindung im Thread erstmal anzutesten gefällt mir! Zwar nicht das schönste von der Welt, aber ich brauche in diesem Falle leider das mit dem geringsten Aufwand. (Das Programm ist eh heillos verloren was Wartbarkeit angeht - da muss ich beizeiten mal eine Renovierung anbieten...) Das probiere ich mal!
Ich hatte zwischenzeitlich einfach mal probiert bloß das ".Active := true" in einem Thread zu machen, und dabei ist mir das komplette Programm mit den wildestens Meldungen und manchmal auch gar keinen auf etliche Weisen um die Ohren geflogen :D. Es hilft vermutlich auch nicht, dass ich in den Event-Handlern reichlich Dinge im UI mache. (Ich vermute mal, dass die dann nämlich auch auf einmal im Thread-Kontext landen.) |
AW: Socket-Verbindung herstellen ohne zu blockieren
[OT?]
Verwendet der Kunde Power-Line oder ähnliches? Bei mir führt das auch immer mal zu Verbindungsunterbrechungen. [/OT?] |
AW: Socket-Verbindung herstellen ohne zu blockieren
Mit der Verlagerung des Sockets in einen Thread bei einer gewachsenen Anwendung hatte ich das Problem der hängenden Programmoberfläche auch mal erfolgreich gelöst. Du musst dann natürlich alle Zugriffe auf den Socket im Thread abhandeln und den Zugriff von außen via TMonitor, CriticalSection oder etwas in der Art absichern.
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. |
AW: Socket-Verbindung herstellen ohne zu blockieren
Die ICS kann ich auch nur wärmstens empfehlen. Falls Threading angedacht ist, auf jeden Fall dies hier lesen:
![]() Sherlock |
AW: Socket-Verbindung herstellen ohne zu blockieren
@stahli: Ich muss zugeben mich mit den Interna des Kundennetzes nicht eingehend beschäftigt zu haben. Ich bin quasi Netzwerklegastheniker, und die haben eine IT-Abteilung, die gerne möglichst wenig kommuniziert. Ich komme quasi mit meinen Kabeln daher und stöpsel mich an, und wenn nicht geht was gehen sollte sage ich: Hier, kaputt. Hilf. (Und das ist gewünscht so, die lassen keinen anderen an ihr Heiligtum. Was ich auch ein wenig verstehen kann.)
Bzgl. ICS: Danke für den Tipp! Für dieses Projekt im speziellen würde ich solch einen großen Umbau gern tunlichst vermeiden, aber für die Zukunft schau ich mir das definitiv mal an. Kurze Nachfrage: Wo liegt der Vorteil zu den Indys (mit denen ich für neue Projekte auch schon geliebäugelt hatte)? |
AW: Socket-Verbindung herstellen ohne zu blockieren
Ohne allzu fit in dem Thema zu sein, aber kannst du nicht in einen Thread parallel zu der alten TClientSocket-Komponente mit der WinApi einfach nur prüfen, ob der Ziel-Socket da/offen/erreichbar ist und das Ergebnis in den Mainthread synchronisieren und entsprechend reagieren?
![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:56 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz