Wie TCP/UDP Client richtig schließen?

  • C#

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Wie TCP/UDP Client richtig schließen?

    Ich bin wirklich überfragt wie man ein TCP/UDP Client richtig schließt ohne eine Exception auszulösen.

    Ich habe hier einmal den Code für den TCP Client
    Spoiler anzeigen

    C#-Quellcode

    1. public class TCP {
    2. #region Variables
    3. public TcpClient socket;
    4. private NetworkStream stream;
    5. private Packet receivedData;
    6. private byte[] receiveBuffer;
    7. #endregion
    8. public void Connect()
    9. {
    10. socket = new TcpClient {
    11. ReceiveBufferSize = dataBufferSize,
    12. SendBufferSize = dataBufferSize
    13. };
    14. receiveBuffer = new byte[dataBufferSize];
    15. socket.BeginConnect(instance._iP, instance._port, ConnectCallback, socket);
    16. }
    17. public void Disconnect(bool fromError)
    18. {
    19. if (fromError) {
    20. instance.Disconnect(true); // Ruft diese Disconnect Methode auf nur vom einem anderen Ort
    21. }
    22. else {
    23. if (socket != null) {
    24. socket.Close();
    25. socket.Dispose();
    26. socket = null;
    27. }
    28. if (stream != null) {
    29. stream.Close();
    30. stream.Dispose();
    31. stream = null;
    32. }
    33. if (receivedData != null) {
    34. receivedData.Dispose();
    35. receivedData = null;
    36. }
    37. receiveBuffer = null;
    38. }
    39. }
    40. private void ConnectCallback(IAsyncResult ar)
    41. {
    42. try {
    43. socket.EndConnect(ar);
    44. if (!socket.Connected) {
    45. throw new Exception("Unknown error.");
    46. }
    47. stream = socket.GetStream();
    48. receivedData = new Packet();
    49. stream.BeginRead(receiveBuffer, 0, dataBufferSize, ReceiveCallback, null);
    50. Main.instance.Log(string.Format("Successfully connected to {0}:{1}.", instance._iP, instance._port.ToString()), false);
    51. instance.IsConnected = true;
    52. instance.Connected?.Invoke(instance, EventArgs.Empty);
    53. }
    54. catch (Exception ex) {
    55. Main.instance.Log("Could not connect to server. Details: " + ex.Message, false);
    56. instance.CouldNotConnect?.Invoke(instance, EventArgs.Empty);
    57. instance.Disconnect(false);
    58. }
    59. }
    60. public void SendData(Packet packet)
    61. {
    62. try {
    63. if (socket != null) {
    64. if (stream != null) stream.BeginWrite(packet.ToArray(), 0, packet.Length(), null, null);
    65. }
    66. }
    67. catch (Exception ex) {
    68. Main.instance.Log(string.Format("Error sending data to server via TCP. Exception: {0}", ex.Message), false);
    69. }
    70. }
    71. private void ReceiveCallback(IAsyncResult ar)
    72. {
    73. try {
    74. if (socket != null) {
    75. if (stream != null) {
    76. if (receivedData != null) {
    77. int _byteLength = stream.EndRead(ar);
    78. if (_byteLength <= 0) {
    79. instance.Disconnect(true);
    80. return;
    81. }
    82. byte[] _data = new byte[_byteLength];
    83. Array.Copy(receiveBuffer, _data, _byteLength);
    84. receivedData.Reset(HandleData(_data));
    85. stream.BeginRead(receiveBuffer, 0, dataBufferSize, ReceiveCallback, null);
    86. }
    87. }
    88. }
    89. }
    90. catch (Exception ex) {
    91. Main.instance.LogErrorToFile(ex, new List<string>() { @"Error in: Client\TCP\ReceiveCallback" });
    92. Main.instance.Log(string.Format("Error while receiving TCP data. If you manually disconnected, then ignore this error. Details: {0}", ex.Message), false); // Info
    93. Game.DisplayText("GTAIVOnline: Error while receiving TCP data. Check the console for details.", 5000); // Info
    94. instance.Disconnect(true);
    95. }
    96. }
    97. private bool HandleData(byte[] data)
    98. {
    99. int _packetLength = 0;
    100. receivedData.SetBytes(data);
    101. if (receivedData.UnreadLength() >= 4) {
    102. _packetLength = receivedData.ReadInt();
    103. if (_packetLength <= 0) {
    104. return true;
    105. }
    106. }
    107. while (_packetLength > 0 && _packetLength <= receivedData.UnreadLength()) {
    108. byte[] _packetBytes = receivedData.ReadBytes(_packetLength);
    109. ThreadManager.ExecuteOnMainThread(() =>
    110. {
    111. using (Packet packet = new Packet(_packetBytes)) {
    112. int _packetID = packet.ReadInt();
    113. packetHandlers[_packetID].Invoke(packet);
    114. }
    115. });
    116. _packetLength = 0;
    117. if (receivedData.UnreadLength() >= 4) {
    118. _packetLength = receivedData.ReadInt();
    119. if (_packetLength <= 0) {
    120. return true;
    121. }
    122. }
    123. }
    124. if (_packetLength <= 1) {
    125. return true;
    126. }
    127. return false;
    128. }
    129. }



    Und hier einmal der UDP Code
    Spoiler anzeigen

    C#-Quellcode

    1. public class UDP {
    2. #region Variables
    3. public UdpClient socket;
    4. public IPEndPoint iPEndPoint;
    5. #endregion
    6. #region Constructor
    7. public UDP()
    8. {
    9. iPEndPoint = new IPEndPoint(IPAddress.Parse(instance._iP), instance._port);
    10. }
    11. #endregion
    12. public void Connect(int localPort)
    13. {
    14. socket = new UdpClient(localPort);
    15. socket.Connect(iPEndPoint);
    16. socket.BeginReceive(ReceiveCallback, null);
    17. using (Packet packet = new Packet()) {
    18. SendData(packet);
    19. }
    20. }
    21. public void Disconnect(bool fromError)
    22. {
    23. if (fromError) {
    24. instance.Disconnect(true); // Ruft diese Disconnect Methode auf nur vom einem anderen Ort
    25. }
    26. else {
    27. iPEndPoint = null;
    28. if (socket != null) {
    29. socket.Close();
    30. socket.Dispose();
    31. socket = null;
    32. }
    33. }
    34. }
    35. public void SendData(Packet packet)
    36. {
    37. try {
    38. packet.InsertInt(instance._iD);
    39. if (socket != null) socket.BeginSend(packet.ToArray(), packet.Length(), null, null);
    40. }
    41. catch (Exception ex) {
    42. Main.instance.Log(string.Format("Error sending data to server via UDP. Exception: {0}", ex.Message), false);
    43. }
    44. }
    45. private void ReceiveCallback(IAsyncResult ar)
    46. {
    47. try {
    48. if (socket != null) {
    49. byte[] _data = socket.EndReceive(ar, ref iPEndPoint);
    50. socket.BeginReceive(ReceiveCallback, null);
    51. if (_data.Length < 4) {
    52. instance.Disconnect(true);
    53. return;
    54. }
    55. HandleData(_data);
    56. }
    57. }
    58. catch (Exception ex) {
    59. Main.instance.LogErrorToFile(ex, new List<string>() { @"Error in: Client\UDP\ReceiveCallback" });
    60. Main.instance.Log(string.Format("Error while receiving UDP data. If you manually disconnected, then you can ignore this error. Details: {0}", ex.ToString()), false); // Info
    61. Game.DisplayText("GTAIVOnline: Error while receiving UDP data. Check the console for details.", 5000); // Info
    62. Disconnect(true);
    63. }
    64. }
    65. private void HandleData(byte[] data)
    66. {
    67. using (Packet packet = new Packet(data)) {
    68. int packetLength = packet.ReadInt();
    69. data = packet.ReadBytes(packetLength);
    70. }
    71. ThreadManager.ExecuteOnMainThread(() => {
    72. using (Packet packet = new Packet(data)) {
    73. int packetID = packet.ReadInt();
    74. packetHandlers[packetID](packet);
    75. }
    76. });
    77. }
    78. }



    Nun löst der UDP Code folgende Exception aus wenn ich ihn disconnecten will
    Spoiler anzeigen

    System.ObjectDisposedException: Auf das verworfene Objekt kann nicht zugegriffen werden.
    Objektname: "System.Net.Sockets.Socket".
    bei System.Net.Sockets.Socket.EndReceiveFrom(IAsyncResult asyncResult, EndPoint& endPoint)
    bei System.Net.Sockets.UdpClient.EndReceive(IAsyncResult asyncResult, IPEndPoint& remoteEP)
    bei GTAIVOnline.Network.Client.UDP.ReceiveCallback(IAsyncResult ar)


    Und der TCP Code löst dies aus
    Spoiler anzeigen

    System.ObjectDisposedException: Auf das verworfene Objekt kann nicht zugegriffen werden.
    Objektname: "System.Net.Sockets.NetworkStream".
    bei System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
    bei GTAIVOnline.Network.Client.TCP.ReceiveCallback(IAsyncResult ar)


    Beide Exceptions sind eigentlich selbsterklärend. Das Objekt ist Disposed es wird aber versucht auf dieses zuzugreifen.
    Aber ich weiß trotzdem nicht wie man sie richtig disconnected. Muss ich TCP Socket/Stream in einer bestimment Reihenfolge schließen?

    Meine Frage ist jetzt wie man nun TCP/UDP Client richtig disconnecten/schließen kann?
    Ich möchte keine Exception beim schließen erhalten, das kommt mir so nach einem dirty weg rüber die Clients zu schließen.
    Wenn ich dir auf irgendeiner Art und Weise helfen konnte, drück doch bitte den "Hilfreich" Button :thumbup:

    Für VB.NET Entwickler: Option Strict On nicht vergessen!
    @ClonkAndre Bei welchen Code-Zeilen kommen die Exceptions?
    Fakt ist, dass Dein Programm auf die Objekte zugreifen will, obwohl sie bereits zerstört sind.
    Setze die Variablen nach dem Disposen auf null und teste beim Zugriff, ob sie verschieden von null sind.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Die TCP Exception kommt von Zeile 86 das wäre diese Zeile

    ClonkAndre schrieb:

    int _byteLength = stream.EndRead(ar);


    Und die UDP Exception kommt von Zeile 56 das wäre diese Zeile

    ClonkAndre schrieb:

    byte[] _data = socket.EndReceive(ar, ref iPEndPoint);


    RodFromGermany schrieb:

    Setze die Variablen nach dem Disposen auf null

    Das macht die Disconnect Methode ja bereits. Sie setzt die Variablen nach dem Disposen auf null.
    Wenn ich dir auf irgendeiner Art und Weise helfen konnte, drück doch bitte den "Hilfreich" Button :thumbup:

    Für VB.NET Entwickler: Option Strict On nicht vergessen!

    ClonkAndre schrieb:

    Das macht die Disconnect Methode ja bereits.
    Das sieht mir so aus, als ob da ein anderer Thread das Objekt zerstört.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hmm.. Sonst greift nichts anderes auf die Disconnect Methode zu außer die instance.Disconnect(true); Methode (Welche einfach nur dazu da ist um die Disconnect Methoden von der TCP und UDP Klasse aufzurufen)

    Die instance.Disconnect Methode:

    C#-Quellcode

    1. public void Disconnect(bool triggerDisconnectEvent, bool fromError = false)
    2. {
    3. if (IsConnected) {
    4. tcp.Disconnect(fromError);
    5. udp.Disconnect(fromError);
    6. tcp = null;
    7. udp = null;
    8. Main.instance.serverInformations = null;
    9. isNetworkReady = false;
    10. isTCPReady = false;
    11. isUDPReady = false;
    12. if (triggerDisconnectEvent) {
    13. Main.instance.Log("Disconnected from the server.", false);
    14. Disconnected?.Invoke(this, EventArgs.Empty);
    15. }
    16. IsConnected = false;
    17. }
    18. }


    Wenn ich dir auf irgendeiner Art und Weise helfen konnte, drück doch bitte den "Hilfreich" Button :thumbup:

    Für VB.NET Entwickler: Option Strict On nicht vergessen!
    Meinst Du damit diese Zeile?

    ClonkAndre schrieb:

    Disconnected?.Invoke(this, EventArgs.Empty);


    Das ist einfach nur ein Event das invoked wird wenn man disconnected.
    Ich abonniere es in einer anderen Klasse und setzte einfach eine Variable zu false, welche aber noch nicht verwendet wird.
    Wenn ich dir auf irgendeiner Art und Weise helfen konnte, drück doch bitte den "Hilfreich" Button :thumbup:

    Für VB.NET Entwickler: Option Strict On nicht vergessen!
    @ClonkAndre OK.
    Wenn Du das Event so deklarierst:

    C#-Quellcode

    1. private event EventHandler<EventArgs> Disconnected = delegate { };
    musst Du keinen null-Test durchführen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!