Entwickler-Ecke
Netzwerk - TcpClient ist geschlossen, wenn ich was senden will
OlafSt - Mo 11.09.17 11:51
Titel: TcpClient ist geschlossen, wenn ich was senden will
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 ?
OlafSt - 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()); } } } |
Th69 - 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 - 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(); } } |
Delete - Di 12.09.17 14:46
- Nachträglich durch die Entwickler-Ecke gelöscht -
Ralf Jansen - 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 - 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:
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: 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:
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: 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.
Delete - Di 12.09.17 17:37
- Nachträglich durch die Entwickler-Ecke gelöscht -
OlafSt - 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.
Th69 - 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 - 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 :oops:
Th69 - Mi 13.09.17 10:30
In Zeile 18 deiner geposteten
CheckReceive()-Methode schließt du doch den Stream - und damit die Verbindung:
(bzw. zusätzlich noch durch das
using in Zeile 14)
:roll:
OlafSt - Mi 13.09.17 11:27
:!: :!: :idea: :evil:
Natürlich... Using ruft Dispose auf :oops: 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.
Delete - Mi 13.09.17 15:28
- Nachträglich durch die Entwickler-Ecke gelöscht -
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!