Laden...

Chat über UDP, Fehler bei Nachrichtenübertragung..

Erstellt von philka007 vor 2 Jahren Letzter Beitrag vor 2 Jahren 1.297 Views
P
philka007 Themenstarter:in
6 Beiträge seit 2022
vor 2 Jahren
Chat über UDP, Fehler bei Nachrichtenübertragung..

Hallo,
Ich versuche verzweifelt ein Tutorial nachzubauen. Dabei soll eine einfache App zum Chatten über das UDP-Protokoll erstellt werden (direkt, Client zu Client)..
Hier der Link:

https://www.c-sharpcorner.com/UploadFile/97ec13/how-to-make-a-chat-application-in-C-Sharp/

Habe nun das Projekt als WPF aufgesetzt und bekomme noch 2 Fehler:

  1. Fehler bei:

sck.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref epRemote, new AsyncCallback(MessageCallBack), buffer);

Hier sagt VisualStudio, dass IPEndpoint nicht in EndPoint konvertiert werden kann... es geht da um das epRemote, welches ich vorher als IPEndPoint definiert habe...

und 2. Fehler bei:


sck.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ref epRemote, new AsyncCallback(MessageCallBack), buffer);

beschwert sich VisualStudio, dass das 5. Argument (wieder epRemote) mit out übergeben werden muss... habe viele Varianten mit out versucht, aber keine wird akzeptiert..

Ich hoffe es ist einfach nur ein kleiner Syntaxfehler..

Danke für jede Hilfe!

Philka007

4.939 Beiträge seit 2008
vor 2 Jahren

Hallo und willkommen,

bitte zuerst immer in die Doku schauen: Socket.BeginReceiveFrom.
Der 5 Parameter ist dort als ref System.Net.EndPoint deklariert, d.h. du darfst hier direkt kein IPEndPoint übergeben. Lege eine temporäre Variable, wie in dem Beispiel der Doku, dazu an:


epRemote = new IPEndPoint(IPAddress.Parse(textFriendsIp.Text), Convert.ToInt32(textFriendsPort.Text))
EndPoint tempRemoteEP = (EndPoint)epRemote; // der Cast ist aber eigentlich überflüssig hier

Oder du deklarierst epRemote gleich als EndPoint (dies ist ja die Basisklasse von IPEndPoint) - so ist es wohl in dem Chat-Code gemacht (leider gibt es dort ja keinen Download des vollständigen Codes).

Beim 2. Fehler meinst du auch BeginReceiveForm (und nicht BeginReceive)?!

P
philka007 Themenstarter:in
6 Beiträge seit 2022
vor 2 Jahren

Besten Dank für die Hilfe, konnte die Fehler beseitigen!

Beim zweiten Fehler war es auch "BeginReceiveFrom".

P
philka007 Themenstarter:in
6 Beiträge seit 2022
vor 2 Jahren

Also Compilieren klappt jetzt, aber wenn ich eine Nachricht senden möchte, wird eine Ausnahme ausgelöst, die ich mal hier reinstelle:

Fehlermeldung:
System.InvalidOperationExcerption: Der aufgerufene Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Bestiz eines anderen Threads befindet.
bei System.Windows.Threading.Dispatcher,VerifyAccess()
bei
Sydtem.Windows.DependencyObject.GetValue(DependencyProperty dp)
bei System.Windows.Control.TextBox.get_Text()
bei ChattAAppPC.MainWindows.MessageCallback(IAsyncResult aResult)
in ...\MainWindows.xaml.cs Zeile 103.

Es geht um diese Zeilen:


private void buttonStart_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                // binding socket
                IPEndPoint epLocal = new IPEndPoint(IPAddress.Parse(textLocalIp.Text),
                Convert.ToInt32(textLocalPort.Text));
                IPEndPoint epRemote = new IPEndPoint(IPAddress.Parse(textFriendsIp.Text),
                    Convert.ToInt32(textFriendsPort.Text));
                sck.Bind(epLocal);
                // connect to remote IP and port
                
                sck.Connect(epRemote);
                // starts to listen to an specific port
               byte[] buffer = new byte[1500];
        EndPoint tempRemoteEP = (EndPoint)epRemote;
                sck.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref tempRemoteEP, new
                AsyncCallback(MessageCallBack), buffer);
                // release button to send message
                buttonSend.IsEnabled = true;
                buttonStart.Content = "Connected";
                buttonStart.IsEnabled = false;
                textMessage.Focus();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

void MessageCallBack(IAsyncResult aResult)
        {

            

            try
            {
                
                IPEndPoint epRemote = new IPEndPoint(IPAddress.Parse(textFriendsIp.Text),
                    Convert.ToInt32(textFriendsPort.Text));
                EndPoint tempRemoteEP = epRemote;
                int size = sck.EndReceiveFrom(aResult, ref tempRemoteEP);
                // check if theres actually information
                if (size > 0)
                {
                    // used to help us on getting the data
                    byte[] receivedData = new byte[1464];
                    // getting the message data
                    receivedData = (byte[])aResult.AsyncState;
                    // converts message data byte array to string
                    ASCIIEncoding eEncoding = new ASCIIEncoding();
                    string receivedMessage = eEncoding.GetString(receivedData);
                    // adding Message to the listbox
                    listMessage.Items.Add("Friend: " + receivedMessage);
                }
                // starts to listen the socket again
                byte[] buffer = new byte[1500];
                sck.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref tempRemoteEP, new AsyncCallback(MessageCallBack), buffer);
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.ToString());
            }


            }

private void buttonSend_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                // converts from string to byte[]
                System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
                byte[] msg = new byte[1500];
                msg = enc.GetBytes(textMessage.Text);
                // sending the message
                sck.Send(msg);
                // add to listbox
                listMessage.Items.Add("You: " + textMessage.Text);
                // clear txtMessage
                textMessage.Clear();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

In den Zeilen ist erstmal der Eventhandler für den Button zum Starten/Verbinden (buttonStart_Click(object sender, RoutedEventArgs e)), dann die Rückruffunktion (MessageCallBack(IAsyncResult aResult)) und der Eventhandler für den Button zum Senden der Nachricht (buttonSend_Click(object sender, RoutedEventArgs e)).
Die Zeile 103 enthält den folgenden Code in der Rückruffunktion:


IPEndPoint epRemote = new IPEndPoint(IPAddress.Parse(textFriendsIp.Text),
                    Convert.ToInt32(textFriendsPort.Text));

Leider habe ich dazu nichts gefunden.. hatte erst gedacht, dass es am IPEndPoint liegt, den ich ja zweimal benutze.. hatte versucht ihn beim zweiten mal anders zu nennen, aber das hat auch nichts gebracht.
Vielleicht mag sich das noch mal jemand angucken, wäre sehr dankbar!

Gruß,

philka007

D
152 Beiträge seit 2013
vor 2 Jahren

Auf UI Elemente darf nur auf dem UI-Thread zugriffen werden.

[FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

4.939 Beiträge seit 2008
vor 2 Jahren

Und noch ein Hinweis: Lagere deine Netzwerk-Funktionalität (Socket) in eine eigene Klasse aus und rufe dann von den UI-Methoden die Methoden dieser neuen Klasse auf ("Trennung von UI und Logik" bzw. Datenzugriff - außerdem werden deine Methoden dann auch übersichtlicher).

Solche Tutorials zeigen leider häufig den Gesamtcode nur in einer Hauptklasse, das hat mit gutem Design (Wartbarkeit, Wiederverwendbarkeit, etc.) aber nichts zu tun.

P
philka007 Themenstarter:in
6 Beiträge seit 2022
vor 2 Jahren

Vielen Dank für eure Hinweise!
Ich habe das Problem mit dem Threadcrossing in der Art gelöst:


Dispatcher.BeginInvoke(new Action(() =>
                        {
                            listMessage.Items.Add("Friend: " + receivedMessage);
                        }));

Das ist vielleicht nicht elegant, aber erstmal ausreichend für mich.
Nun konnte ich zumindest schonmal eine Nachricht jeweils "hin und her" senden.. leider klappt das Senden und Empfangen nur einmal, danach sieht man die gesendeten Nachrichten nur jeweils bei sich selbst und sie erreichen den Chatpartner nicht.
Habt ihr dazu vielleicht noch einen Hinweis? Ich hätte gedacht, dass der Code nach dem Empfangen einer Nachricht immer wieder auf eingehende Nachrichten wartet.

4.939 Beiträge seit 2008
vor 2 Jahren

Dafür sollte eigentlich der Code unterhalb von // starts to listen the socket again zuständig sein.

Was ich aber jetzt erst sehe:


// used to help us on getting the data
byte[] receivedData = new byte[1464];
// getting the message data
receivedData = (byte[])aResult.AsyncState;

Der Code (sowie der Kommentar) ist Blödsinn. Es wird unnötig ein Array angelegt (und warum ausgerechnet diese Zahl?), um dann in der nächsten Zeile wieder überschrieben zu werden (so daß das vorherige Array vom GC wieder entsorgt werden muß).

Es reicht


// getting the message data
byte[] receivedData = (byte[])aResult.AsyncState; // bzw. var

(Das ist leider bei vielen Anfängern zu sehen, daß sie meinen, sie müßten ein Objekt initialisieren, bevor es, meistens direkt in der folgenden Zeile, zugewiesen wird!)

PS: Selbiges auch in buttonSend_Click...

P
philka007 Themenstarter:in
6 Beiträge seit 2022
vor 2 Jahren

Es reicht
// getting the message data
byte[] receivedData = (byte[])aResult.AsyncState; // bzw. var
(Das ist leider bei vielen Anfängern zu sehen, daß sie meinen, sie müßten ein Objekt initialisieren, bevor es, meistens direkt in der folgenden Zeile, zugewiesen wird!)

PS: Selbiges auch in buttonSend_Click...

Danke für die Hilfe, das leuchtet mir schon mal ein!

Ich sehe leider noch nicht, was ich in dem Teil falsch gemacht habe, der weiter auf Nachrichten warten soll:






// starts to listen the socket again
                    // getting the message data
                    byte[] buffer = new byte[1500];
                    sck.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref tempRemoteEP, new AsyncCallback(MessageCallBack), buffer);


.. habe es auch in anderen Beispielen so gefunden und leider klappt die Nachrichtenübertragung nur ein mal. Ein Fehler wird nicht angezeigt.

4.939 Beiträge seit 2008
vor 2 Jahren

Das ist aus der Ferne nicht zu beantworten - da mußt du einfach debuggen (oder loggen) und schauen, ob es am Senden oder Empfangen liegt.

Und bei einer ausgelagerten Netzwerk-Funktionalität könntest du dies auch elegant(er) über Unit-Tests testen: [Artikel] Unit-Tests: Einführung in das Unit-Testing mit VisualStudio

P
philka007 Themenstarter:in
6 Beiträge seit 2022
vor 2 Jahren

Danke für den Input, werde es versuchen!

T
2.224 Beiträge seit 2008
vor 2 Jahren

Anbei würde ich dir auch empfehlen sich mal den UdpClient aus .NET anzuschauen.
Dort wird der Socket für die Udp Verbindung gekapselt und nimmt dir damit einiges an Arbeit ab.
Ist auch vom Code her etwas kompakter und sauberer als dein jetziger Ansatz.

Link:
UdpClient .NET

Nachtrag:
Die Begin/EndReceiveFrom Methoden sind noch das Asynchrone Programmierungsmuster aus .NET 2.0 Zeiten.
Dies wird zwar noch supportet, ist aber technisch outdatet.

Hier solltest du dir zukünft Tasks mit async/await anschauen.
Dann kannst du die *Async Methoden von UdpClient/Socket verwenden.
Damit würde sich auch der Zugriff auf die Controls sauber lösen lassen.

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.