Autor |
Beitrag |
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mo 11.09.17 11:51
Hallo Freunde,
ich habe hier ein seltsames Problem.
Kurz umrissen habe ich einen Thread (kein Task), der mit Hilfe eines TcpListeners auf eingehende Verbindungen lauert. Wenn sich eine Verbindung aufbaut, wird der dazu passende tcpCLient in einer ConcurrentBag gespeichert. Die Anwendung, die diesen TcpListener erstellt, schickt nun nach gusto irgendwelche Daten an alle verbundenen tcpClients. Das funktioniert auch soweit ganz hervorragend.
Nun möchte diese Anwendung aber an einen spezifischen tcpClient etwas senden. Meine Idee war es nun, das tcpClient-Objekt umher zu reichen. Das tcpClient-Objekt wird auch nicht zerstört oder sonstwie modifiziert, es wird nur herumgereicht. Bis es zum Sendevorgang kommt.
Ich prüfe in der Sende-Warteschlange, ob diese Nachricht spezifisch ist. Ist dem So, wird in der ConcurrentBag der passende tcpClient gesucht. Ein Vergleich mittels "tcpClient.Client.Handle == " funktioniert und ich könnte nun Daten senden.
Aber:
C#-Quelltext 1: 2:
| if (c.Connected) c.Client.Send(TheData.ToArray); |
c.Connected ist immer false, die Verbindung also geschlossen.
Ich habe schon mit
C#-Quelltext 1:
| tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); |
was versucht, aber ohne Erfolg.
Warum ist die Verbindung denn geschlossen ?!? Ich rufe nirgendwo ein Close auf (weder Client- noch Serverseitig), für ein Timeout ist die Anfangsphase des Handshaking zwischen den beiden viel zu aktiv, so das dort keines aufkommen kann.
Ich bin ratlos. Hat einer von euch eine Idee ?
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Th69
Beiträge: 4786
Erhaltene Danke: 1055
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mo 11.09.17 12:08
Hallo,
schon in die Doku geschaut: TcpClient.Connected? Wenn noch keine Nachricht gesendet oder empfangen wurde, ist diese Eigenschaft false.
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mo 11.09.17 12:28
Jop:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
| private void Receive(CancellationToken ct) { TcpClient tcpClient;
listener.Start(); while (!ct.IsCancellationRequested) { if (listener.Pending()) { tcpClient = listener.AcceptTcpClient(); tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); Clients.Add(tcpClient); tcpClient.Client.Send(Data.ToArray()); } } } |
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Th69
Beiträge: 4786
Erhaltene Danke: 1055
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mo 11.09.17 13:39
OK, aber ist denn c.Connected == true direkt nach dem Senden?
Zusätzlich kannst du ja mal in einem eigenen Timer (oder Thread/Task) diese Eigenschaft sekündlich ausgeben lassen, ob und wann sich diese ändert.
Verbindet sich denn währenddessen ein anderer TcpClient (und wird dann vllt. die Eigenschaft der anderen TcpClients wieder zurückgesetzt)?
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Di 12.09.17 13:23
Womöglich ist dieses Problem völlig anders gelagert.
Die Daten, die dort hin- und her gehen, werden durch einen XMLSerializer erzeugt. Folgende Klasse soll serialisiert / deserialisiert werden:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
| [Serializable()] public class GUIThreadData { public enum GUIThreadEventType { GTET_NONE, GTET_DEBUG, GTET_LOGIN, GTET_REQUEST, GTET_INFO } public enum GUIThreadEventSubType { GTST_NONE, GTST_STARTUP, GTST_SHUTDOWN, GTST_SERIAL }
public GUIThreadEventType EventType; public GUIThreadEventSubType EventSubType; public byte EventDataByte; public int EventDataInt; public double EventDataDouble; public string EventInfo; [NonSerialized()] public object UserData; } |
Beim Serialisieren wird nun eine Exception geworfen, das TcpClient ein unerwarteter Typ sei. Doch das entsprechende Feld ist doch mit NonSerialized gekennzeichnet ? Womöglich ist dieses Kennzeichen verkehrt ?
Der Serializer-Aufruf selbst ist unspektakulär:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| public static byte[] SerializeToArray(GUIThreadData GTD) { using (MemoryStream ms = new MemoryStream()) { XmlSerializer xml = new XmlSerializer(typeof(GUIThreadData)); xml.Serialize(ms, GTD); return ms.ToArray(); } } |
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Di 12.09.17 14:46
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
Ralf Jansen
Beiträge: 4705
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Di 12.09.17 15:04
Das NonSerialized Attribut gehört zu den Formattern (z.B. dem BinaryFormatter für binäres serialisieren) der XmlSerializer reagiert auf andere Attribute. Hier zum Beispiel wäre XmlIgnore eher das Richtige.
Moderiert von Th69: C#-Tags hinzugefügt
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Di 12.09.17 17:21
Ich zeige mal den Code, ich sehe den Wald vor Bäumen nicht.
Das wesentliche dieser Assembly ist der Thread-Code:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76:
| public void Run() { GUIReceiverThread = new Thread(new ThreadStart(PreReceive)); GUIReceiverThread.Start(); }
private void PreReceive() { Receive(ct.Token); }
private void Receive(CancellationToken ct) { TcpClient tcpClient;
listener.Start(); while (!ct.IsCancellationRequested) { CheckReceive();
if (listener.Pending()) { tcpClient = listener.AcceptTcpClient(); tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); Clients.Add(tcpClient); GUIThreadData GTD = new GUIThreadData(); GTD.EventType = GUIThreadData.GUIThreadEventType.GTET_REQUEST; GTD.EventSubType = GUIThreadData.GUIThreadEventSubType.GTST_SERIAL; tcpClient.Client.Send(GUIThreadData.SerializeToArray(GTD)); continue; }
if (!GUIEvent.WaitOne(100)) continue;
if (Clients.Count < 1) continue;
if (GUIEventData.Count < 1) continue;
while (GUIEventData.Count > 0) { if (GUIEventData.TryDequeue(out GUIThreadData GTD)) { foreach (TcpClient c in Clients) { if (GTD.UserData != null) { TcpClient cc = GTD.UserData as TcpClient; if (cc.Client.Handle != c.Client.Handle) { continue; } } c.Client.Send(GUIThreadData.SerializeToArray(GTD)); } } } } listener.Stop(); } |
Nun die Methode CheckReceive, die für das Empfangen von Nachrichten zuständig ist:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33:
| private void CheckReceive() { byte[] buffer; List<GUIThreadData> GTL;
foreach (TcpClient c in Clients) { if (!c.Client.Connected) continue; if (c.Available < 1) continue; buffer = new byte[c.Available]; using (NetworkStream ns = c.GetStream()) { ns.Read(buffer, 0, c.Available); string Message = Encoding.UTF8.GetString(buffer, 0, buffer.Length); ns.Close(); GTL = GUIThreadData.DeSerializeFromString(Message); foreach (GUIThreadData gtd in GTL) { gtd.UserData = c; ReceiveData.Enqueue(gtd); } OnReceived(new EventArgs()); } } } |
Und zum Schluß noch das OnReceive-Event:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8:
| private void OnReceived(EventArgs e) { OnReceiveData?.Invoke(this, e); } |
Die Liste "Clients" ist eine einfache ConcurrentBag<TcpClient>, GUIEventData und ReceiveData sind simple ConcurrentQueue<GUIThreadData>.
Mit diesem simplen Thread nimmt der Dienst seine Kommunikation mit der Außenwelt auf. Das funktioniert hervorragend, bis ich auf etwas empfangenes eine Antwort senden will. Ergo:
- Server sendet einen Request (wie man direkt nach dem AcceptClient sehen kann)
- Dieser Request kommt am Client an und wird korrekt verarbeitet.
- Der Client sendet eine Antwort zurück (hier: Eine Nummer zur Prüfung)
- Der Server empfängt diese Antwort korrekt und verarbeitet diese
- Der Server will nun entweder sagen: "Jo, des is gut" oder "nee, lass ma lieber". Dann erfolgt diese Exception, weil der Socket nicht mehr existiert.
Oh, ehe ich es vergesse: Ja, das ist alles Public. Ich hatte zuerst mit einer Struct gedanklich gespielt, es dann aber doch mit einer Klasse gemacht. GUIThreadData ist eine reine Klasse zum Daten umhertransportieren, keine Logik. Vielleicht ist eine struct da wirklich besser geeignet ? Bin für Vorschläge offen.
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Di 12.09.17 17:37
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Di 12.09.17 18:26
Der Fehler entsteht hier:
C#-Quelltext 1:
| c.Client.Send(GUIThreadData.SerializeToArray(GTD)); |
Das Umstellen von object auf TcpClient hat nicht geholfen. War aber eine gute Idee.
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Th69
Beiträge: 4786
Erhaltene Danke: 1055
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mi 13.09.17 09:34
Geht es hier um die Serialisieren-Exception, dann hat Ralf Jansen das doch schon beantwortet?!
Ansonsten poste mal den genauen Text der Exception.
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mi 13.09.17 09:47
Ohje, da ist etwas durcheinander geraten.
Also: Die Exception durch den Serializer war nur eine mögliche Spur - durch eure Hilfe mit dem XmlIgnore-Attribut konnte diese Exception erfolgreich eliminiert werden. Hat aber mein Problem nicht gelöst.
Nach wie vor entsteht diese Exception:
Error 1: 2:
| Unbehandelte Ausnahme: System.ObjectDisposedException: Auf das verworfene Objekt kann nicht zugegriffen werden. Objektname: "System.Net.Sockets.Socket". |
an dieser Stelle:
C#-Quelltext 1:
| c.Client.Send(GUIThreadData.SerializeToArray(GTD)); |
wenn folgendes abläuft:
- Client verbindet sich
- Server sendet einen Request (wie man direkt nach dem AcceptClient sehen kann)
- Dieser Request kommt am Client an und wird korrekt verarbeitet.
- Der Client sendet eine Antwort zurück (hier: Eine Nummer zur Prüfung)
- Der Server empfängt diese Antwort korrekt, stellt sie in die ReceiveData-Queue und löst einen Event aus
- Der Mainthread des Servers verarbeitet diese Antwort und erzeugt eine Gegen-Antwort
- Diese Gegen-Antwort, die der MainThread erzeugt hat, wird in die GUIEventData-Queue gesteckt, die unser Problem-Thread dann abarbeitet
- Der Thread möchte die Antwort aus der GUIEventData-Queue an den entsprechenden Client senden. Dann erfolgt diese Exception, weil der Socket nicht mehr existiert.
Man sieht, das die Kommunikation eigentlich prima funktioniert, so lange ich nicht an einen besonderen TcpClient senden will. Offenbar wird der Verweis auf den TcpClient in der GUIThreadData-Struktur irgendwann ungültig - ich weiß nur nicht, warum.
Ich hoffe, man versteht, was ich da fasel
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Th69
Beiträge: 4786
Erhaltene Danke: 1055
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mi 13.09.17 10:30
In Zeile 18 deiner geposteten CheckReceive()-Methode schließt du doch den Stream - und damit die Verbindung:
C#-Quelltext
(bzw. zusätzlich noch durch das using in Zeile 14)
|
|
OlafSt
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mi 13.09.17 11:27
Natürlich... Using ruft Dispose auf Wald, Bäume, bla.
Aber wir haben dann einen Fehler in der Dokumentation seitens Microsoft. Dort heißt es:
Zitat: | By default, closing the NetworkStream does not close the provided Socket. If you want the NetworkStream to have permission to close the provided Socket, you must specify true for the value of the ownsSocket parameter. |
(Hervorhebung durch mich)
Offensichtlich stimmt das dann nicht so ganz. Oder der von TcpClient.getStream zurückgegebene NetworkStream ist anders vorbelegt.
Jetzt funktioniert es so, wie es soll. Danke für eure Hilfe, das hätte ich nie gefunden.
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1
|
Verfasst: Mi 13.09.17 15:28
- Nachträglich durch die Entwickler-Ecke gelöscht -
|
|
|