Laden...

Dictionary in TCP Threads

Erstellt von RalfsFW26 vor 5 Jahren Letzter Beitrag vor 5 Jahren 1.723 Views
R
RalfsFW26 Themenstarter:in
9 Beiträge seit 2018
vor 5 Jahren
Dictionary in TCP Threads

Hallo,

Ich bin auch voller Anfänger und versuche mich an einigen Windows Formen.
Zur Zeit beschäftigt mich aber die Netzwerkverbindung zwischen 2 Computern.

Das Senden und Empfangen funktioniert. Genutzt habe ich einfach die beispiele:

TCPListener

und

TCPClient

Mein Problem ist, das ich aus einer Windows-Form nicht auf meine empfangenen Daten aus der jeweiligen Klasse zugreifen kann. Innerhalb der Klasse kann ich die empfangen Daten per Console auslesen. Das ist aber auch das einzige.

Habe die letzten Tage schon viel probiert und gegoogelt, aber nix gefunden, was mir helfen würde.

Hoffe ihr könnt mir bitte helfen.

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

da fehlen dir sicher Basics, die du eher in einem Buch nachlesen solltest wenn du nicht nicht quälen möchtest.

Kurz gesagt:
In der Konsole wirkt das für Anfänger simpler, da im Standard dort alles "static" markiert ist, was so mit Windows Forms nicht funktioniert.

Du kannst dir mal folgenden FAQ-Thread anschauen - vll hilft es dir schon weiter:
[FAQ] Kommunikation von 2 Forms

LG

R
RalfsFW26 Themenstarter:in
9 Beiträge seit 2018
vor 5 Jahren

Danke erstmal für deine schnelle Antwort.

Auf den von dir gesendeten Link bin ich gestern auch schon gestoßen.

zwischen zwei Formen bzw. von Form1 auf eine Textbox in Form2, kann ich zugreifen, nur nicht auf einen string. Und schon gar nicht aus einer klasse.

habe zum Spaß den Listener ohne static erstellt. geht auch ne.
Ganz ehrlich ich kapier das auch ne. Zumindest ist mir noch kein Licht aufgegangen, wie ich das hinbekommen könnte. Der "AHA"-Gedanke fehlt mir noch.

funktioniert:
in Form1:

Form2 frm = new Form2();
string test = frm.textbox1.test;

funktioniert nicht: (string data = "Hallo" in Form2)

Form2 frm = new Form2();
string test = frm.data;
funktioniert nicht: 
Class1 class1 = new Class1();
string übergabe= class1.data;

Ich weis ne ob mir ein Verweis fehlt, oder ob ich das nur über Umwege bekomme.

16.841 Beiträge seit 2008
vor 5 Jahren

Mit "funktioniert nicht" kann niemand was anfangen.
[Hinweis] Wie poste ich richtig? Punkt 5

1.029 Beiträge seit 2010
vor 5 Jahren

Hi,

naja - du musst verstehen, dass "Form2" eine Klasse gibt, von der es theoretisch und praktisch unzählige Instanzen geben kann. (die nennst du "frm" in deinen Beispielen)

Obendrauf gibt es die Modifier wie z.B. "private", "public", etc., die dafür sorgen, dass manche Eigenschaften eben nicht außerhalb des jeweiligen Klassencodes geändert werden können. Auf eigene hast du Einfluss - auf andere nicht - und das ist auch gut so.

Grundlegend musst du dir erst mal ein Konzept machen - was du eigentlich vorhast ist ja offenkundig, dass du jeweils eine Instanz von Form1 und Form2 hast, die jeweils miteinander kommunizieren können sollen. (Entweder nur in eine Richtung, ggf. aber auch in beide Richtungen) - für diese Kommunikation hast du ja die Wege im verlinkten Thread gesehen.

Was dir aktuell auf die Füße fällt ist der Sachverhalt mit "private" und "public"-Modifiern - schützt dich eigentlich vor Dummheiten - aber man muss es wissen. (Controls sind deshalb im Standard mit private-Modifiern versehen - zum Verständnis kannst dir mal die Form1.Designer.cs anschauen) Es ist nämlich gut, dass Form2 die TextBox von Form1 nicht verändern kann - Controls sollten eigentlich niemals öffentlich zugreifbar sein. Um's einfach zu halten (und so macht man's eigentlich nicht, sondern geht wie geschildert eher über Events) mal folgendes Beispiel, wie du dir den Text einer Textbox mehr oder minder verfügbar machen könntest:


using System;

namespace WindowsFormsApp3
{
    static class Program
    {
        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main()
        {
            var frm1 = new Form1();
            var frm2 = new Form2();
            frm2.MyProperty = "MyValue";
        }
    }
}

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public string MyProperty
        {
            get { return textBox1.Text; }
            set
            {
                // validate data here !!!
                textBox1.Text = value;
            }
        }
    }

public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        public string MyProperty
        {
            get { return textBox1.Text; }
            set
            {
                // validate data here !!!
                textBox1.Text = value;
            }
        }
    }


Den vorgenannten Quellcode brauchst du nicht kopieren - alleine wird er dir nicht helfen. Zudem musst du eben schauen, dass du eben an den benötigten Codestellen auch auf die entsprechenden Instanzen zugreifen kannst - aber wie gesagt: das braucht ein Konzept deinerseits.

Nun in aller Deutlichkeit: Bevor du weiter rumexperimentierst (anders kann man das nicht nennen) - nimm dir ein Buch / Onlinebuch (gibts kostenlos) oder irgendeinen Kurs. Andernfalls wirst du nicht weit kommen.

LG

4.941 Beiträge seit 2008
vor 5 Jahren

Mein Problem ist, das ich aus einer Windows-Form nicht auf meine empfangenen Daten aus der jeweiligen Klasse zugreifen kann.

Stichwort: Ereignis (event), s. [FAQ] Eigenen Event definieren / Information zu Events (Ereignis/Ereignisse)

5.658 Beiträge seit 2006
vor 5 Jahren

Weeks of programming can save you hours of planning

R
RalfsFW26 Themenstarter:in
9 Beiträge seit 2018
vor 5 Jahren

Danke euch erstmal,
werde mich bei den Events mal reinlesen. Soweit hab ich noch gar ne gedacht.

Habe mich immer nur auf die Sache mit der Form zu Form (wie im zweiten Post von mir geschrieben) konzentriert und verbissen.

R
RalfsFW26 Themenstarter:in
9 Beiträge seit 2018
vor 5 Jahren
TCP Verbindungen, Dictionary und Threads

Nachdem ich mein vorheriges Problem über Events lösen könnte, nun zu einem weiteren Problem. Wie ist es mir möglich über prozesse hinaus Daten abzulegen. Habe es über ein Dictionary versucht die Clients zu speichern.

Hauptthread (Server) Dictionary erstellt
|
|----Thread 1 (Listener) / hier wird jeder Client hinzugefügt.
| |
| |----Thread 2 (Client 1)
| |
| |----Thread 3 (Client 2)
| |
| |----Thread 4 (Client 3)
| |
| usw.
|
|---- Senden über den Server / hier sollen Der TCPClient mit dem byte[]
von der Dictionary ausgelesen werden

Das senden von jedem Client zu dem Server funktioniert. Aber wenn ich vom Server zurück senden möchte, kommt die Halte-Meldung (kein Key im Wörterbuch)
Greif ich die Daten im Thread 1 ab, funktioniert's. Nur vom Hauptthread aus is das "Wörterbuch" immer leer.

Kann man die Daten nicht in einem übergeordneten Thread abrufen?

T
2.224 Beiträge seit 2008
vor 5 Jahren

Wie sieht den die aktuelle Lösung im Code aus?
Und was genau meinst du mit Daten zwischen Prozessen abzulegen?
Meinst du hier nur für deinen TCP Server/Clients feste Strukturen oder generell?
Per TCP musst du natürlich die Datenstrukturen definieren da sowohl Client als auch Server wissen müssen welche Daten diese senden bzw. empfangen.
In der Regel wird dies eben über selbst definierte Byte Blöcke vorgegeben damit Client und Server auch mit einander kommunizieren können.
Bei anderen Protokollen wie UDP oder bei HTTP Lösungen wie WebAPI, SOAP und co. gibt es dann definierte Request/Response Formate.
Hängt halt immer vom Anwendungsfall ab welche Prozesse wie miteinander Daten austauschen sollen.

Wenn du die Daten zwischen den Threads teilen willst, wirst du eine der fertigen Concurrent Collections verwenden.
Diese beiten dann schon intern auch gleich Thread Locks um parallele Zugriffe abzusichern.

Link:
https://msdn.microsoft.com/de-de/library/dd287191(v=vs.110).aspx

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

R
RalfsFW26 Themenstarter:in
9 Beiträge seit 2018
vor 5 Jahren

Fehler der angezeigt wird:

Die Anwendung befindet sich im Haltemodus

Ihre App wurde angehalten, aber es gibt keinen anzuzeigenden Code, da alle Threads externen Code ausgeführt haben (normalerweise System- oder Frameworkcode).

System.Collections.Generic.KeyNotFoundException: "Der angegebene Schlüssel war nicht im Wörterbuch angegeben."

Der Server soll den TCPClient-Wert (und die bytes) in die ConcurrentDictionary schreiben, was er auch im Thread 1 macht.

Beim Daten senden zurück (Server -> Client) soll der Server sich die Daten aus den ConcurrentDictionary holen und dem richtigen Client antworten. Es funktioniert ja alles soweit nur das senden eben nicht. Das Hauptproblem ist, Das ich im Hauptthread auf eine Leere ConcurrentDictionary zugreife, aber im Thread 1 ist sie gefüllt und abrufbar(konnte ich über die Console testen)...


using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Concurrent;

    public delegate void PacketDaten(string stringtext, byte[] data, int bytesRead, TcpClient client, int status);

    public class Server
    {
        public event PacketDaten OnDataReceived;

        private TcpListener listener;
        static ConcurrentDictionary<TcpClient, NetworkBuffer> clientBuffers;
        private NetworkBuffer newBuffer;
        private int sendBufferSize = 1024;
        private int readBufferSize = 1024;
        private int port;
        private bool started = false;

        public Server(int port)
        {
            this.port = port;
            clientBuffers = new ConcurrentDictionary<TcpClient, NetworkBuffer>();
        }

Abblauf nur hören und Daten empfangen:

Der Server soll permanent auf Clients hören (Thread 1)


        public void Start()
        {
            listener = new TcpListener(IPAddress.Any, port);
            Console.WriteLine("Server gestartet: " + port);

            Thread thread = new Thread(new ThreadStart(ListenForClients));
            thread.Start();
            started = true;
        }

Sobald sich ein Client angemeldet hat, akzeptiert der Server den Client und erstellt einen Arbeitsthread nur für diesen Client (Thread 2)

Jeder Client bekommt seinen eigenen Arbeitsprozess


        private void ListenForClients()
        {
            listener.Start();

            while (started)
            {
                TcpClient client = listener.AcceptTcpClient();
                Thread clientThread = new Thread(new ParameterizedThreadStart(ArbeitsThreadClient));
                Console.WriteLine("New client connected");

                newBuffer = new NetworkBuffer();
                newBuffer.WriteBuffer = new byte[sendBufferSize];
                newBuffer.ReadBuffer = new byte[readBufferSize];
                newBuffer.CurrentWriteByteCount = 0;
                clientBuffers.GetOrAdd(client, newBuffer);
                clientThread.Start(client);
                Thread.Sleep(15);
            }
        }

Im jeweiligen Arbeitsprozess:


        private void ArbeitsThreadClient(object client)
        {
            int status = 1;
            TcpClient tcpClient = client as TcpClient;
            if (tcpClient == null)
            {
                Console.WriteLine("TCP client is null, stopping processing for this client");
                DisconnectClient(tcpClient);
                return;
            }

            NetworkStream clientStream = tcpClient.GetStream();
            int bytesRead;

            while (started)
            {

                bytesRead = 0;
                String stringtext = null;
                try
                {
                    bytesRead = clientStream.Read(clientBuffers[tcpClient].ReadBuffer, 0, readBufferSize);
                    stringtext = System.Text.Encoding.ASCII.GetString(clientBuffers[tcpClient].ReadBuffer, 0, bytesRead);
                }
                catch
                {
                    Console.WriteLine("A socket error has occurred with client: " + tcpClient.ToString());
                    break;
                }

                if (bytesRead == 0)
                {
                    break;
                }

                if (OnDataReceived != null)
                {
                    OnDataReceived(stringtext, clientBuffers[tcpClient].ReadBuffer, bytesRead, tcpClient, status);
                }


                Thread.Sleep(15);
            }
            Console.WriteLine("Client Disconnect");
            DisconnectClient(tcpClient);
            status = 0;
        }

    private void DisconnectClient(TcpClient client)
    {
        if (client == null)
        {
            return;
        }

        Console.WriteLine("Disconnected client: " + client.ToString());

        client.Close();

        NetworkBuffer buffer;
        clientBuffers.TryRemove(client, out buffer);
    }

Ablauf Daten senden


        public void Paket(byte[] data, TcpClient client)
        {
            if (clientBuffers[client].CurrentWriteByteCount + data.Length > clientBuffers[client].WriteBuffer.Length)
            {
                Senden(client);
            }

            Array.ConstrainedCopy(data, 0, clientBuffers[client].WriteBuffer, clientBuffers[client].CurrentWriteByteCount, data.Length);

            clientBuffers[client].CurrentWriteByteCount += data.Length;
            Senden(client);
        }

        private void Senden(TcpClient client)
        {
            client.GetStream().Write(clientBuffers[client].WriteBuffer, 0, clientBuffers[client].CurrentWriteByteCount);
            client.GetStream().Flush();
            clientBuffers[client].CurrentWriteByteCount = 0;
        }


    public class NetworkBuffer
    {
        public TcpClient Client;
        public byte[] WriteBuffer;
        public byte[] ReadBuffer;
        public int CurrentWriteByteCount;
    }


T
2.224 Beiträge seit 2008
vor 5 Jahren

Ein kleine Hinweis:
Erstell dir nicht pro Client einen Thread über die Thread Klasse.
Lass .NET hier den ThreadPool verwenden in dem du mit Tasks arbeitest.
Sonst muss .NET für jedes Thread Objekt extra einen Thread vom OS erstellen lassen, was durch den Threadpool ausgespart wird.

Auch dürfte dann dein Code sauberer werden, da deine Task Methode dann direkt den TCPClient als Parameter bekommen kann, was das unnötige Boxing spart.

Auch ist dein Dictionary hier doch komplett überflüssig.
Den Buffer kannst du auch im Client Thread erstellen, dann muss der Server nichts davon wissen.
Ebenfalls blockierst du deine Verarbeitung mit dem Concurrent Dictionary wenn viele Clients gleichzeitig sich verbinden und verarbeitet werden.

Lass die Buffer hier im jeweiligen Client Thread erstellen und arbeiten.
Somit hat jeder Client Thread auch seine eigenen Resourcen unabhängig vom Server.
Somit kapselst du auch die Daten besser ab, da der Server die Daten dann im Client Thread kennt und verarbeiten kann.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

R
RalfsFW26 Themenstarter:in
9 Beiträge seit 2018
vor 5 Jahren

Danke erstmal für deine Tipps,

Habe jetzt aus den Threads Tasks gemacht und die Dictionary entfernt.

Jeder Task hat seine eigenen Daten. TCPClient Zuordnung, readbuffer,writebuffer und CurrentWriteByteCount.

Ich empfange die Daten wie bisher ohne Probleme von jedem Client, aber wie kann ich von der senden-methode jetzt auf den richtigen Task zugreifen?

Also wenn ich an 2 Clients eine Nachricht schicken möchte.

Ich rufe meine senden-methode aus der Form auf. Wie bringe ich jetzt die Methode dazu auf den richtigen Task zuzugreifen um an die Daten zukommen

Entweder muss sich die Methode in den Task einklinken oder sich die Daten aus dem jeweiligen Task holen.

Wenn ich bei MSDN oder docs.microsoft schaue ist mir das echt zu unverständlich erklärt, zumindest mit den Tasks

T
2.224 Beiträge seit 2008
vor 5 Jahren

Deine Senden Methode dürfte doch innerhalb deiner Server Klasse sein, wie alle anderen Methoden auch.
Dann musst du doch nur den Buffer als weiteren Parameter an die Senden Methode geben.
Die Senden Methode muss nur den TCP Client sowie die Versand Daten bekommen um ihre aktuelle Funktion beizubehalten.
Den jeweiligen Task brauchst du hier nicht, nur eben die wirklich grundsätzlichen Daten für die Verarbeitung der Methode.

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

R
RalfsFW26 Themenstarter:in
9 Beiträge seit 2018
vor 5 Jahren

Ach natürlich.

Manchmal sieht man den Wald vor lauter Bäumen nicht.

Supi, funktioniert.
Danke.