Delphi serversocket, kliens thread bezárás

Delphi serversocket, kliens thread bezárás
2017-01-05T23:25:21+01:00
2017-01-09T09:48:33+01:00
2022-12-04T07:05:35+01:00
bigyusz
Sziasztok,
Adott egy program, mely ez alapján készült: TServerSocket in multithread mode 
Aki a témában jártas, az szerintem már a címéből is sejti, miről van kb. szó. Persze át lett gyúrva, van benne egy felesleges ciklus is, meg a hibakezelés is olyan amilyen (mármint a linken), de most nem ez a lényeg.
A kérdésem arra irányulna, hogy mikor létrehozzuk a kliensnek a szálat:

SocketStream := TWinSocketStream.Create(ClientSocket, 30000);
Akkor ugyebár a második paraméterben adjuk meg a szál timeout értékét ms-ban. 
Viszont tegyük fel, én hamarabb be akarom zárni a socketet, de a timeout még nem járt le. Van rá bármi mód, hogy bezárjam a (akár több ilyen) sockete(ke)t?
Ugyanis addig, amíg nem kapcsolódik le az összes kliens és/vagy nem jár le a timeout, még a programot sem lehet bezárni.
Erre keresnék valami megoldást.
Előre is köszönöm szépen!
Mutasd a teljes hozzászólást!
Annyival kiegészítve, hogy ha kicsire veszed a timeout-ot, akkor ne bontsd a kapcsolatot, ha nem jött adat, csak ignoráld! Vagyis a közepét így:
if SocketStream.Read(Data,SizeOf(Data))>0 Then Begin RecText:=''; RecText:=Data; if Length(RecText)>0 Then //szinkronizalva a formnak End;
szerk. természetesen bigyusznak szól!
Mutasd a teljes hozzászólást!

  • A close metódus miért nem jó?

    Az általad küldött link FormClose metódusa:

    Procedure TForm1.FormClose(Sender: TObject; Var Action: TCloseAction); Begin ServerSocket.Close; End;
    Mutasd a teljes hozzászólást!
  • Így stThreadBlocking módban nem lehet bezárni, amíg le nem járt a timeout vagy le nem csatlakozott a kliens(ek).
    Mutasd a teljes hozzászólást!
  • Nos, akkor alapjaiban rossz a megoldásod. Kulon szalon kommunikalsz, letre hozol egy objektumot, mely kulon szalon futtatja az alabbi while-ciklust, 

    while(not_canceled) begin // socket probal olvasni, viszonylag rovid timeouttal < 1 sec // ha timeout volt semmi baj, ujra varakozik // ha adat jott egy eventen keresztul // kiszol az aplikacionak, hogy csinaljon vele vmit... end
    Mikor le akkarod allitani a programodat a not_caqnceled erteket false-ra allitod...
    Mutasd a teljes hozzászólást!
  • Először is, köszönöm válaszod!
    Szóval akkor ez amit lent itt írok megoldás így nem jó, teljesen máshogy kell a szálat kezelni? (Ha nem jó, akkor miért?)
    Serversocket stThreadBlocking -ra állítva, és a GetThread esemény hozza létre a szálat, amint alább is látható.
    Ez már az én programomból van, csak a lényeg kimásolva:

    //socket getthread procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread); begin // Create a new thread for connection SocketThread := TFileServerThread.Create(False, ClientSocket); end; procedure TFileServerThread.ClientExecute; var Data: Array[0..240] of ansichar; RecText: String; SocketStream: TWinSocketStream; OwnTimeout:integer; begin try OwnTimeout:=Form1.ClientTimeout; SocketStream := TWinSocketStream.Create(ClientSocket, OwnTimeout); While Not Terminated And ClientSocket.Connected Do Try FillChar(Data, 240, 0); if SocketStream.Read(Data, SizeOf(Data)) = 0 Then Begin ClientSocket.Close; Terminate; End; RecText:=''; RecText := Data; if Length(RecText)>0 then begin // <itt szinkronizálva átadom a formnak az adatot> end; Except HandleException; End; finally SocketStream.Free; End; End;
    Egyébként így működik, próbáltam, egyidejűleg kb. 50-60 klienssel is, folyamatos kommunikáció mellett hiba nélkül kiszolgálta a klienseket (órákon át), és a gép erőforrásain gyakorlatilag meg se látszódott. Csak a fent írt kapcsolatbontásos kérdés merült fel bennem..
    Amit Te írtál, szálat, akkor az nem olyan megoldás, mint az itteni? (Az általam linkelt kódot hanyagoljuk, mert abban van egy rossz részlet, amit most itt írtam, az legyen a mérvadó).
    Mutasd a teljes hozzászólást!
  • Qurwareg programoztam Delphiül, de a fenti kód teljesen jónak tűnik, csak az OwnTimeout-ot kell levenni kisebbre és már készen is vagy.

    Mikor le akarod állítani a programodat, akkor az összes TFileServerThread objektumon beállítod, a Terminated értékét true-ra. És így hibátlanul kéne működnie....
    Mutasd a teljes hozzászólást!
  • Annyival kiegészítve, hogy ha kicsire veszed a timeout-ot, akkor ne bontsd a kapcsolatot, ha nem jött adat, csak ignoráld! Vagyis a közepét így:
    if SocketStream.Read(Data,SizeOf(Data))>0 Then Begin RecText:=''; RecText:=Data; if Length(RecText)>0 Then //szinkronizalva a formnak End;
    szerk. természetesen bigyusznak szól!
    Mutasd a teljes hozzászólást!
  • Neked is köszönöm válaszod!
    Viszont ha kiveszem ezt a részt:

    if SocketStream.Read(Data, SizeOf(Data)) = 0 Then Begin //... egyéb teendők elvégzése ClientSocket.Close; Terminate; End;
    és a kliens megszakítja a programmal a kapcsolatot, akkor nem zárul be socket, illetve a szál, a timeoutban megadott idő után sem, csak ha lezárom a szervert (serversocket.close).

    Elvira: 
    Hogyan tudom a Terminated-et beállítani true-ra a főszálból? Sajnos az a baj, hogy a főszálból nem tudok egyszerűen hivatkozni a különböző (vagy akár az összes) thread-ekre.
    Próbálkoztam azzal is, hogy a

    While Not Terminated And ClientSocket.Connected Do
    részt kiegészítettem egy változóval, amit a főszálból állítok, pl:

    While Not Terminated And ClientSocket.Connected And CanRun Do
    ahol a "CanRun"-t a főszálból állítva záratnám be a szálat, de ez nem akar működni, nem történik semmi.

    Az is jó lenne, ha a szálakra egyenként tudnék hivatkozni. Egyébként a TFileServerThread.ClientExecute, meg pár serversocket eseményben le tudom kérdezni a thread ID-t, de pl egy buttonból nem tudok rájuk hivatkozni, vagyis nem tudom hogyan lehet (bár erre nincs szükség, ha amit írtál/írtatok, sikerül megoldani).
    Mutasd a teljes hozzászólást!
  • A Terminated az egy adattagja, propertyje vmelyik objektumban. Nem láttam a teljes kódot, de valószínűleg TFileserverThread -e. Ezeket a thread példányokat kéne beletenni egy listába. És mikor a program leáll akkor végig szalad a tömbön/listán és mindegyikre beállítja a Terminated -et...
    Mutasd a teljes hozzászólást!
  • Ez jól hangzik, kérdés, hogyan tudom "betenni" őket egy listába. A program lényege igazából az, amit fentebb írtam, a többi "csak körítés". Csatoltam képet a TFileserverThread objektumairól. Ennek van egy "CurrentThread" nevű objektuma, aminek rengeteg további objektuma van, pl. ott a "Terminate" is, csak nem tudom hogyan hivatkozzak a Thread-(ek)re.
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • A 13:23-ás hszedben :

    //socket getthread procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread); begin // Create a new thread for connection SocketThread := TFileServerThread.Create(False, ClientSocket); end;
    itt hozod létre....

    Ezt az instanceot kéne eltenni egy listába...
    Mutasd a teljes hozzászólást!
  • Megpróbáltam, de lehet butaságot csináltam.
    Szóval a próba erejéig deklaráltam egy ilyen globális objektumot:

    ClientTh1: TServerClientThread;
    Majd "kimentettem" a szálat:

    //socket getthread procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread); begin // Create a new thread for connection SocketThread := TFileServerThread.Create(False, ClientSocket); ClientTh1:=SocketThread; end;
    És miután csatlakozok 1 klienssel, a szerveren egy gombbal lezárnám:

    procedure TForm1.Button2Click(Sender: TObject); begin ClientTh1.Terminate; end;
    De ez nem igazán vezet megoldásra, nem zárja be a szálat amíg kliens oldalról nem bontom a kapcsolatot. Bár pl. a ClientTh1.ThreadID-re már a valós ID-t adja vissza, szóval valahogy sikerül az átadás, csak nem kerek ez még nekem.
    Mutasd a teljes hozzászólást!
  • Ha jol ertem es mindent egyszerre be akarsz fejezni, akkor eleg egy globalis flag, amit minden thread figyel.
    A masik visszajelzest nem ertem! Ha a client kapcsolat megszakad, akkor a .Connected false lesz, nem?
    Mutasd a teljes hozzászólást!
  • Próbáltam a globális flaget, de nem akart működni: itt.
    Persze, ha a client bontja a kapcsolatot, akkor a connected false lesz, ezért bezárul a szál.

    Én már régóta gyanakodok arra, hogy ezt a fajta szálat/kapcsolatot nem lehet szerver oldal felől csak drasztikusan bontani (mint ahogy eddig: kilőni feladatkezelőből). Ez lehetséges? Mármint hogy ezt nem lehet megoldani? És pl. máshogy kell a szálat létrehozni, vagy ilyesmi?
    Mutasd a teljes hozzászólást!
  • De meg lehet oldani. 
    Itt az ideje hogy új projektet nyiss. Kezd el atmasolni a kommunikáció szempontjából fontos kodreszleteket. Eközben érts még mit miért csinálsz... Egyes részeket teszteld.
    Mutasd a teljes hozzászólást!
  • Azt gondolom, hogy mivel a While után nem zárod le a socket-et, a thread sem fog leállni. Így csinálnám:
    While Not Terminated And ClientSocket.Connected And CanRun Do Try FillChar(Data, 240, 0); if SocketStream.Read(Data, SizeOf(Data))>0 Then Begin RecText:=''; RecText := Data; if Length(RecText)>0 then begin // <itt szinkronizálva átadom a formnak az adatot> end; end; Except HandleException; End; finally ClientSocket.Close; Terminate; SocketStream.Free; End;
    A lényeg, hogy ha bármi okból kiesünk a ciklusból, akkor a kapcsolatot bontjuk (Close), a szálat pedig lelőjük (Terminate). Így a CanRun globális változónak is működnie kell.
    Mutasd a teljes hozzászólást!
  • Ezt mindenkinek írom, sikerült egy nagyot lépnem előre szerintem (csodákra képes az alvás  ).
    Szóval lekopaszítottam szinte 0-ra a programot.
    És rájöttem, hogy amíg nem jár le a timeout, addig igazából nem tudok a főszálból beavatkozni ebbe a TFileServerThread-be. Addig hiába állítom a "CanRun" változót, meg csinálok akármit, egyszerűen nincs hatással a szálra (igen, tudom, este ezt írtátok, de konkrétan ez nem jött át nekem). Azonban amikor lejár ez a timeout, már működik a dolog. Mihelyst átállítom a változót, rögtön reagál a szál, és bontja a klienssel a kapcsolatot. Azonban itt megint felmerült egy dolog, ami zavar. Sajnos nem bontja valami szépen a kapcsolatot, mert a kliens oldalon egy 10053-as socket error hibaüzenet kapok, és a kliens mielőtt újra csatlakozik, nyomni kell neki egy socket close-t. Nyilván ez kivitelezhető, de nem tartom jó megoldásnak, főleg, hogy le tudja zárni szépen is a kapcsolatot, mert ha pl. a timeout esemény lejártakor adom ki a ClientSocket.Close és Terminate parancsot (amit írtatok, hogy ne így legyen) , akkor a kliens oldalon is szabályosan, hiba nélkül megtörténik a lekapcsolódás.
    Másik kérdés, nem baj hogy lejár a timeout? Hiszen itt azt írják, hogy utána nem lehet írni-olvasni a sockettel: link. Bár ami érkezik a klienstől, azt a programban tudom kezelni (szóval a lenti program szerint, megjelenik a memo-ban a szöveg).
    A progi:

    //thread execute procedure TFileServerThread.ClientExecute; var Data: Array[0..240] of ansichar; RecText: String; SocketStream: TWinSocketStream; begin SocketStream := TWinSocketStream.Create(ClientSocket, 100); While Not Terminated And ClientSocket.Connected and form1.canrun Do begin FillChar(Data, 240, 0); if SocketStream.Read(Data, SizeOf(Data))>0 Then Begin RecText:=''; RecText := Data; if Length(RecText)>0 then begin Synchronize( procedure begin Form1.Memo1.Lines.Add(RecText); end); end; end; end; ClientSocket.Close; Terminate; SocketStream.Free; end;
    Szerk.: 100ms-ra van beállítva a timeout, de ha pl. 10sec-re van, akkor a 10 másodpercig nem zárja le, csak ha a kliens bontja a kapcsolatot.
    Sajnos elég rosszul tagolja az oldal a forráskódot, nem lehet rajta szépíteni :(
    Mutasd a teljes hozzászólást!
  • További észrevétel, hogy így viszont a kliens nem tudja kezdeményezni a kapcsolat bontását. Illetve ahogy bontanám kliens oldalon a kapcsolatot, a fenti kódban a while ciklus eszeveszetten elkezd pörögni, és felmegy a proci használat 13%-ra. És azt a kódban nem tudom detektálni jelenleg, hogy a timeout járt le, vagy a kliens bontotta a kapcsolatot (mindkét esetben a SocketStream.Read 0-t ad).
    További észrevétel, hogy úgy néz ki, a while ciklusban csak akkor fut le bármi is, ha a klienstől jön valami, vagy lejár a timeout. 
    A timeout pedig ha lejár, akkor elölről kezdődik, mert ha pl. 5 másodpercre állítom be, akkor 5 másodpercenként végrehajtódik a while ciklus.
    Mutasd a teljes hozzászólást!
  • "További észrevétel, hogy úgy néz ki, a while ciklusban csak akkor fut le bármi is, ha a klienstől jön valami, vagy lejár a timeout."
    Ezt akartuk, csak akkor egyén processzor időt ha jött adat.

    "ahogy bontanám kliens oldalon a kapcsolatot, a fenti kódban a while ciklus eszeveszetten elkezd pörögni" igen mert ilyenkor nem dob exceptiont. Meg kell vizsgálni hogy a kapcsolat el e még ha nem akkor ki kell lépni a whikebol...
    Mutasd a teljes hozzászólást!
  • Meg kell vizsgálni hogy a kapcsolat el e még

    Ez az, amit nem lehet, vagy legalábbis nem tudom hogyan lehet.
    Mutasd a teljes hozzászólást!
  • Elvileg a ClientSocket.Connected adna false értéket, ha a kliens bontott (vagy bármi miatt megszakadt a vonal).
    Ha ezek a dolgok semmiképp nem működnek (nekem is nagyon sok macera volt annak idején), akkor próbálj ki egy olyat, hogy a kliens rendszeresen (timeout-on belül) küld valami semleges kódot, pl. üres sort, és ha nem küld, akkor a szerver oldalon bontsál. Ilyesmi volt az eredeti is, csak ott ugyebár folyamatosan értelmes adatot vártál, azért kellett hosszú timeout, hogy a kliensnek legyen ideje valami küldendőt kitalálni. A lényeg az, hogy rövid timeout-ot lehessen beállítani, hogy rendszeresen (max. pár tizedmásodperc) vizsgálhassál CanRun meg Terminated esetre.
    Mutasd a teljes hozzászólást!
  • Sajnos a ClientSocket.Connected nem akar false lenni, ha a kliens megszakítja a kapcsolatot. Közben észrevettem, hogy az eddigi stabil programban is, ha lejár a timeout (ami mondjuk 5 perc), akkor a kliens oldalon szintén kapok egy 10053-as hibakódot (ettől függetlenül újra tudok kapcsolódni). Viszont az esetek 99,99%-ban a kliens szakítja meg a kapcsolatot, csak ha mégis gebasz van, és mondjuk a programot /szerversocketet újra kell indítani, akkor ne kelljen megvárni a timeout-ot, és a feladatkezelőből se kelljen kilőni a progit. De ahogy próbáltam az itt leírt megoldásokat, azt vettem észre, hogy az általam korábban írt, nagy timeout-ra állított megoldás a legstabilabb, legkevesebb erőforrást igénylő módszer. És szerintem még belefér a "nem kókány" kategóriába. 
    Szóval ezt a funkciót feláldozva, lehet gyáva módra maradok a stabil, biztos megoldásnál.
    Ez esetben viszont köszönöm Nektek a hozzászólásokat, mindenképp hasznosnak találom őket. A pont nem tudom kié legyen, lehet még nyitva hagyom a topicot...
    Mutasd a teljes hozzászólást!
  • Azért még ne add fel!
    Közben folyamatosan azon jár az eszem, hogy miért a ClientExecute ciklus felülírásával dolgozol. Annak idején egy szinttel feljebb (tServerSocket), az OnClientConnect, OnClientRead, OnClientDisconnect események használatával oldottam meg a problémámat. Ezek teljesen megbízhatóan érkeztek rendre. És a bontást a tServerSocket.Close metódussal.

    szerk. igen, tServerSocket-et használtam, nem tFileServerThread-et. Ezt meg kéne nézned, mert szvsz az utóbbi fájlátvitelre való, talán azért van timeout-ja. Esetedben szerintem ez pont hátrány, az lenne a jó, ha a kapcsolat fennállna addig, míg valamelyik vége nem bontja.
    Mutasd a teljes hozzászólást!
  • Mármint a stNonBlocking üzemmódra gondolsz? Azzal az a baj, hogy sok kliens esetén már nem valami üzembiztos a működése.
    Mutasd a teljes hozzászólást!
  • Nem az stNonBlocking módra gondolok, az nem függ össze. A tServerSocket.ServerType lehet stThreadBlocking, ekkor minden kliensnek új szálat nyit. Viszont ezzel semmi dolgod, csak az OnClientRead-ban azonosítanod kell, melyik klienstől jött az adat, és feldolgozni.
    Mutasd a teljes hozzászólást!
  • Ha nem hozok létre saját TFileServerThread.ClientExecute-ot, amiben benne van a Read, akkor nem hajtódik végre az OnClientRead, nyilván, mivel nem történik olvasás.
    Mutasd a teljes hozzászólást!
  • Valamit nagyon elkeffentesz...

    Itt egy komplett példa kliens szerver. Másolás le, fordítsd le...

    Chat Room Socket (Delphi) - RAD Studio Code Examples
    Mutasd a teljes hozzászólást!
  • És ez külön szálba rakja a klienseket? 
    Mutasd a teljes hozzászólást!
  • Nem tudom.
    Mutasd a teljes hozzászólást!
  • Nem rakja. 
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd