TCP-s adatkiküldésnél folyamatos kapcsolat fenntartása
2022-05-07T20:40:19+02:00
2022-06-27T00:04:06+02:00
2022-08-12T09:40:31+02:00
makittib
Sziasztok!
Van egy programom ami minden adatkiküldésnél meghívja a "private void Halozatra_kuldes(byte[] kikuldendo)" eseményt gombnyomásra, ill. még ez 1s-onkénti automatikus adatkiküldésnél is meghívódik ami jól működik.
A probléma az hogy automatikus kiküldésnél minden kiküldésnél bontja a kapcsolatot amit pillanatnyilag nem sikerült megoldanom (mindkét kiküldési módot szeretném hogy működjön).
Kódom:

private void Halozatra_kuldes(byte[] kikuldendo) { if (textBox_IPcim.Text.Length > 8) //IP cím beírásának vizsgálata { try { lb_hibauzenet.Text = ""; textBox_IPcim.BackColor = Color.White; Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); if (autoinditas == 0 & automatikus_kikuldes == 0) { socket.Connect(IPAddress.Parse(textBox_IPcim.Text), 5500); } else { socket.Connect(IPAddress.Parse(textBox_IPcim.Text), 5500); automatikus_kikuldes = 1; } NetworkStream networkStream = new NetworkStream(socket); networkStream.Write(kikuldendo, 0, kikuldendo.Length); this.lb_valasz.Text = "Nem lett kérdezve az eszköz!"; this.textBox_valasz.Text = ""; //törölje az előző választ if (checkBox_valasz.Checked==true) //ha a válaszolás be van pipálva { this.lb_valasz.Visible = true; bool isWork = true; while (isWork) { if (networkStream.DataAvailable) { isWork = false; byte[] valasz = new byte[socket.Available]; networkStream.Read(valasz, 0, valasz.Length); try //válaszadat értelmezése { if (valasz[0] == 255) { string valasza = ""; for (int i = 0; i < valasz.Length; i++) { valasza += String.Format("{0:X2}", valasz[i]); valasza += " "; //hex értékek tagolása szóközzel } valasza = valasza.TrimEnd(' '); //kiküldött karakterek végéről törli a szóközt this.textBox_valasz.Text = valasza; } else { string valasz_string=""; for (int i = 0; i < valasz.Length; i++) { valasz_string += Convert.ToChar(valasz[i]); } this.lb_valasz.Text = "Az eszköz válasza " + valasz_string; } } catch { this.lb_valasz.Text = "Nem válaszolt az eszköz!"; } } } } else { this.lb_valasz.Visible = false; // a label válasz szó elrejtése this.lb_valasz.Text = "Nem lett kérdezve az eszköz!"; this.textBox_valasz.Text = ""; //törölje az előző választ } networkStream.Flush(); networkStream.Close(); if (automatikus_kikuldes == 0) { socket.Close(); } }
Nem annyira egyszerű már a kódom, remélem a lényeg érthető. A segítséget előre is köszönöm, makittib.
Mutasd a teljes hozzászólást!

  • C# nem az én világom, a többi kapcsolódó téma meg nagyon rég volt, kéretik ennek megfelelően kezelni amit írok!

    Szóval a tcp connection alapvetően így működik emlékeim szerint: kapcsolódás -> elküldöd amit akarsz -> jön rá válasz a kapcsolaton és a szerver automatikusan zárja a kapcsolatot.
    Van lehetőség ú.n. perzisztens kapcsolat létrehozására (ezt a google-n kellene megkeresni, hogy hogyan tudod létrehoz i), ilyenkor ha a kliens oldal is támogatja, a szervertől jövő válasz után nem záródik a kapcsolat kb 120mp-ig az utolsó átküldött adat után. De üresjárat esetén előbb-utóbb ez is bezáródik.

    És ismétlem emlékezetből írom, nagyon rég nem foglalkoztam vele, de mivel eddig senki sem válaszolt, hátha segít...
    Mutasd a teljes hozzászólást!
  • Én ilyen esetekben minden olyan objektumból, csak egy példányt hoznék létre, amely a hálózati kapcsolódásért felel (Socket, Networkstream). Ezekből - szerintem - elég egyet is létrehozni, azokat globálisan eltárolni, és fenntartani az alkalmazás futása alatt, és minden egyes híváskor, amikor lefut a Halozatra_kuldes metódus, akkor a már korábban létrehozott struktúrát kell újra felhasználni, a kapcsolat felépítésére. A végén pedig nem kell a close-okat meghívni, elég a flush, illetve csak akkor kell lezárni - bontani - a kapcsolatot, ha az adatfolyam átküldés azt valóban megköveteli, de ha így van, akkor pedig kell lennie valamilyen megoldásnak az egyszer nyitásra is pl.: .open(). Ezt nem tudom, de általában elmondható, hogy nem szükséges külső rendszerek kapcsolódási objektumait újra és újra felépíteni, elég egyszer, és azt használni minden egyes adatcsere során. Ez igaz például egyszerű adatbázis kapcsolatok esetében is, és hálózati átviteleknél is. Az ilyen objektumok létrehozása amúgy is nagy overhead-al jár, szóval kódkarbantartás miatt, illetve teljesítményoptimalizálás miatt is megéri ezeket az objektumokat a memóriában tartani. Például akkor biztosan, ha egy-egy ilyen adatküldési metódus percenként lefut többször is.
    Mutasd a teljes hozzászólást!
  • Először is - ahogyan az más is javasolta, ne zárd be a socket-et. És ne lokáis változóban tárold,  hanem az objektumod adattagjaként. Azután még lehet, hogy szükség lesz a KeepAlive szolgáltatásra, ami, ha jól értem, a háttérben néha küld egy csomagot, hogy a hálózati eszközök ne bontsák el a kapcsolatot. Pl: 
    [Solved] TcpClient communication with server to keep alive connection in c#? - Local Coder
    Mutasd a teljes hozzászólást!
  • Axxtros és Nikknem köszönöm a gyors választ. Sajnos a TCP-s kommunikációval most ismerkedek és eddig csak ennyit sikerült összehoznom. Ezért is kérek segítséget.
    Mutasd a teljes hozzászólást!
  • Bocs, hülyeséget írtam. Annyira http-n lógok mostanában, hogy összekevertem a kettőt. Amit írtam, az a http-re vonatkozik, nem a tcp-re. :(
    Mutasd a teljes hozzászólást!
  • Ha jól értettem a kódodat, akkor ez valójában csak annyiért felelős, hogy elküld egy adatcsomagot, és a másik féltől vár egy nyugtázást, hogy megkapta.

    Én ezt más architektúrával oldanám meg, Először is, ezt az egész TCP kapcsolatért felelős réteget áttenném egy background thread-re, amivel egy in-memory message queue-val kommunikálnék. Erre az in-memory message queue-ra itt már tettem fel egy példát. A példában a Consumer class lesz az az osztály, ami a background thread-en fut és a TCP kapcsolatot tartja fenn. A Producer class pedig az alkalmazásodnak az a kódja lesz, amely előállítja a TCP-n elküldendő adatot.

    Az elv a következő:
    - A background thread (Consumer) várakozik addig, amíg a message queue-ból ki nem tud venni egy elemet (a WorkItem class egy példányát) feldolgozásra. Ha kap üzenetet, azt elküldi, és ha nem kapott a klienstől választ, visszateszi az üzenetet a queue végére, hogy újrapróbálja később (vagy esetleg egy másik queue-n notify-t küldhet az alkalmazásodnak, hogy ez az üzenet nem ment el).
    - Az alkalmazásod, ha adatot akar küldeni a másik félnek, a queue-ba bedob egy üzenetet. Ez a művelet non-blocking, tehát nem tartja fel a végrehajtást.
    Mutasd a teljes hozzászólást!
  • H.Lilla
    Köszönöm a hozzászólást, de nagyon bonyolult a hozzászólásod alapján és én VS2010-ben azaz .net 4.0-ában írok. Nagyvonalakban a programomat jól érted. A pontosság kedvéért: az összeállított byte tömböt küldi át az eszköznek. Opcionálisan a csomag összeállítása olyan hogy az eszköz válaszoljon "if (checkBox_valasz.Checked==true)". Az "autoinditas == 0" igaz esetén gombnyomásra 1 csomagot küldjön az eszköznek, hamis ágában gondoltam "automatikus_kikuldes = 1;", hogy ne legyen új kapcsolódás, de egyszer legyen egy kapcsolódás az eszközhöz.
    Mutasd a teljes hozzászólást!
  • Sajnos a TCP-s kommunikációval most ismerkedek és eddig csak ennyit sikerült összehoznom.

    Valójában, amit elméletben leírtam az objektumok létrehozására, és élettartamára vonatkozóan annak önmagában semmi köze a TCP-hez, hanem általános programozási megoldás, kvázi egy pattern. Csak használható ebben az esetben is.

    ...de nagyon bonyolult a hozzászólásod alapján és én VS2010-ben azaz .net 4.0-ában írok

    Amiről @H.Lilla írt az valóban ad egy plusz - de pozitív - csavart a dolognak, de megfontolandó a bevezetése, mert hasznos a hosszabb folyamatokat egy mellékszálba tenni. Arra itt találsz C# példát.

    Az eredeti kérdésedre viszont a válasz az, hogy a kódod azért bontja a kapcsolatot, mert close-olod a metódus végén a kapcsolatot, majd, egy újabb ráhívásnál létrehozol egy teljesen újat.
    Mutasd a teljes hozzászólást!
  • Axxtros köszönöm a linket, így már a szálakat kezdem érteni. De a programom végén

    if (automatikus_kikuldes == 0) { socket.Close(); }
    a feltétellel direkt csak akkor záródik a kapcsolat ha nincs automatikus kiküldés (0 érték egy küldés, 1 érték automatikus kiküldés-logikai egyértelműbb lenne, csak korábban több állapot miatt így maradt).
    Mutasd a teljes hozzászólást!
  • A close csak az egyik fele a problémának, a valódi gond az, hogy minden egyes lefutás során új socket-et hozol létre:

    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    Utána pedig paraméterként ebből jön létre a NetworsStream objektum is.

    NetworkStream networkStream = new NetworkStream(socket);
    Ha újat hozol létre egy objektumból, akkor - ebben az esetben - az új kapcsolatot is jelent (a korábbi bontása mellett), hisz a korábbi memória beli objektumok a GC alá kerülnek be, aztán törlődnek. Én első körben fenti objektumokat, az ezekből felállított állapotot csak egyszer hoznám létre, és utána minden egyes hívás során ezeket használnám fel a küldéshez. A close nyilván opcionális, de persze minden egyes hívás előtt meg kell vizsgálni, hogy a socket isClose-ban van-e, és ha igen, akkor - gondolom - open-t kell hívni rá.
    Mutasd a teljes hozzászólást!
  • Axxtros!
    Próbáltam feltételbe berakni de mindig hibára futott (futtatni sem engedte). Most a szálkezeléssel küzdök, konzolos verziót nem sikerül átírnom, hiába hozom létre a metódusát teljesen mást, 1 sorost generál a VS2010:
    public ParameterizedThreadStart Socket_kapcsolodasa { get; set; }
    vagy
    private ParameterizedThreadStart Socket_kapcsolodasa;
    Korábban is emiatt kerültem a szálkezelést, pedig egyszerűnek tűnik .
    Mutasd a teljes hozzászólást!
  • Tovább próbálkozva:
    Kapcsolódáskor:

    client = new TcpClient(); serverEndPoint = new IPEndPoint(IPAddress.Parse(ipcim), 5200); client.Connect(serverEndPoint); clientStream = client.GetStream();
    Kiküldés:

    clientStream.Write(teszt_b, 0, teszt_b.Length); clientStream.Flush();
    wireshark-kal figyelve 2 db adatkiküldés után a csatolt hiba lesz (többféleképpen átírva is erre fut)
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • Áttértem visual studio community 2019-re 4.5-osre a programommal. (előkészülve a System.Threading.Tasks-ra)
    De előbb újabb sajnos sikertelen próbálkozásom:

    private void Socket_kapcsolodas(string ipcim, int port) { try { IPAddress ipAddress = IPAddress.Parse(ipcim); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.BeginConnect(new IPEndPoint(ipAddress, 5500), new AsyncCallback(ConnectCallback), null); } catch (Exception ex) { MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void Adathalozatra_kuldese(byte[] kikuldendo, int offset, int size) { try { socket.BeginSend(kikuldendo, 0, kikuldendo.Length, SocketFlags.None, new AsyncCallback(SendCallback), null); } catch (SocketException) { } //szever zárva } private void SendCallback(IAsyncResult ar) { try { socket.EndSend(ar); } catch (SocketException) { } //szever zárva }
    Itt is ugyanaz a jelenség mint az előző hozzászólásomnál (célom megismétlem: kb. 1 sec-onként úgy adatot /byte tömböt/ kiküldeni Tcp-n, hogy ugyanazon a porton legyen a kiküldés /azaz folyamatos kapcsolat legyen/). A segítséget előre is köszönöm, makittib.
    Mutasd a teljes hozzászólást!
  • Sziasztok, további próbálkozásaim:

    private void Socket_kapcsolodas(string ipcim, int port) { try { halozatkapcsolodas = new Thread(t => { client = new TcpClient(ipcim, port); IPAddress ip = IPAddress.Parse(ipcim); kapcsolodas = new TcpListener(ip, port); kapcsolodas.Start();// hiba: System.Net.Sockets.SocketException: 'A kért cím nem érvényes a hozzá tartozó környezetben' }) { IsBackground = true }; //ez a szál háttérszál lesz halozatkapcsolodas.Start(); } catch { //kapcsolodas = null; //nem sikerült }
    Itt a kapcsolodas.Start();-nál lett hiba, Wireshark-nál a kapcsolódásnál valami látható-hibajelzést melléírtam, ha kikommentelem a sort akkor a következőnél is lesz hiba:

    private void Adathalozatra_kuldese(byte[] kikuldendo, int offset, int size) { try { clientStream = client.GetStream();//hiba: System.NullReferenceException: 'Az objektumhivatkozás nincs beállítva semmilyen objektumpéldányra.' clientStream.Write(kikuldendo, 0, kikuldendo.Length); clientStream.Flush(); } catch (SocketException) { } //szever zárva
    itt 2-szer mint korábban a generált byte tömb hálózaton látom hogy elküldésre került, de a 3-ik küldésnél a hibát itt is odaírtam.
    Lassan teljesen kifogyok az ötletekből, hiába próbálok utána olvasni (vagy rossz úton keresek).
    Előre is köszönöm a segítséget
    Mutasd a teljes hozzászólást!
  • Sziasztok!
    Többszöri próbálkozás után továbbra sem akar jól működni, így úgy döntöttem hogy a teljes forrást felrakom (több normálisabb próbálkozásom maradékát ki kommentelve benne hagytam).
    Így hátha egyszerűbb a segítségnyújtás (.net 4.5).
    Jelenleg kb. 10-ik kiküldésre szűnik meg a hálózati küldés a Wireshark-al szerint. De programomban az átküldendő byte tömb tartalmát is megjelenítem egyrészt a programom vizuálisan látható hogy nincs kifagyva és nem szükséges terminálozni mert meg van jelenítve az adatcsomag.
    A segítséget előre is köszönöm, makittib!
    Mutasd a teljes hozzászólást!
    Csatolt állomány
  • Hi, 

    Thread-et sosem példányosítunk .net-ben. Helyette a ThreadPool-t vagy a Task-okat használjuk.

    Látom a kódod .net framework 5.0-ás. Nem lehetne egy kicsit feljebb menni vele , mondjuk 4.8-ra?

    Végig kéne gondolni, hogy mit is akarsz. Elküldelni egy ip-címre egy text folyamot. Akkor erre kéne fokuszálni elsőként. Megírni egy olyan osztályt aki:
    1. Ki tud építeni egy TCP/IP socket kapcsolatot.
    2. Kintről (akár több szálról is) szálbiztosan tudja az adatokat befogadni és ha itt az idő azokat kiküldeni.

    Ha a fenti dolog meg lenne, akkor győztél. A többi csak körítés.
    Mutasd a teljes hozzászólást!
  • Hi, megírtam a DataSender class-t. Ebből egy példányt kell létrehoznod, majd tömködni bele az adatot. Addig míg a kapcsolat él, vagy te magad azt le nem bontod (Close()) ennek simán küldeni kell a bepumpált adatot.

    using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Halozati_kuldes { public class DataSender { private Socket _socket; private CancellationTokenSource _cancellationTokenSource; private AutoResetEvent _event; private object _lock = new object(); private List<byte> _buffer = new List<byte>(); public void Connect(string ip) { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); var endPoint = new IPEndPoint(IPAddress.Parse(ip), 5500); _socket.Connect(endPoint); _cancellationTokenSource = new CancellationTokenSource(); Task.Run(() => { while (!_cancellationTokenSource.IsCancellationRequested) { var res = _event.WaitOne(500); if (!res) // no signal received { continue; } byte[] bytes = null; lock (_lock) { bytes = _buffer.ToArray(); _buffer.Clear(); } if (bytes.Length <= 0) continue; // there is nothing to send while (!_cancellationTokenSource.IsCancellationRequested) { var sentCount = _socket.Send(bytes); if (sentCount == bytes.Length) break; // all data have been sent bytes = bytes.SubArray(sentCount, bytes.Length - sentCount); // remaining bytes to send } } _socket.Close(); // the sending process is halted, close socket }, _cancellationTokenSource.Token); } public void Close() { _cancellationTokenSource.Cancel(); // halt background task } public void SendData(string data) { lock (_lock) { _buffer.AddRange(Encoding.GetEncoding(1250).GetBytes(data)); _event.Set(); // let socket to send data } } } public static class Extensions { public static T[] SubArray<T>(this T[] array, int offset, int length) { T[] result = new T[length]; Array.Copy(array, offset, result, 0, length); return result; } } }
    E
    Mutasd a teljes hozzászólást!
  • Elvira
    Először is köszönöm a segítséget, amit időm engedi írásod szerint fejtesztem tovább.
    A .net a Microsoft Visual Studio Community 2019 Version 16.11.14-ra frissítve korábban így a 4.7.2-őt volt a legfrissebb. De sikerült a ndp48-devpack-enu (azaz a .net 4.8-at rátenni) így már az is megoldható.
    Még egyszer köszönöm, makittib!
    Mutasd a teljes hozzászólást!
abcd