TCP-Trennungsfehler bei Chat

  • VB.NET

Es gibt 17 Antworten in diesem Thema. Der letzte Beitrag () ist von Flash1232.

    TCP-Trennungsfehler bei Chat

    Guten Tag,

    Ich habe ein TCP Multi-Chat-Programm (Client - Server - Client) erstellt und dieses funktioniert auch relativ gut, jedoch hängen sich Server und Client beim Beenden des Clients auf.

    Nach stundenlangem Nachdenken und ausprobieren bin ich jetzt am toten Punkt angelangt. Ich habe etwa 7 Chat-Tutorials studiert und bin daraus auch nicht schlau geworden.

    Der Server gibt zuerst folgenden Fehler aus:



    Danach folgenden Fehler:




    Danach folgenden Fehler:


    Und im Quellcode folgenden Fehler:





    Im Quellcode des Clients der folgende Fehler:







    Ich vermute, dass der Server nicht erkennen kann, ob der Client sich getrennt hat und gibt dann eine unbehandelte Ausnahme an.

    Wie kann ich dem Server sagen, dass er entweder Antworten oder die Verbindung anullieren soll?



    Freundliche Grüsse

    Flash1232
    Hi,

    könntest du bitte, wenn ich mit meinen nächsten Vermutungen falsch lieg, mal den vollständigen Quellcode der etwas mit Verbindungsaufbau, Verbindungsbeenden sowie mit Übertragen und empfangen zu tun hat posten.

    Jetzt kann ich aber schon mal sagen, dass der Server wirklich nicht merkt, dass die Verbindung beendet wurde. Die Fehler musst du mit nem Try..Catch-Block abfangen und die Verbindung ordnungsgemäß beenden. Ich glaube da gibts keinen "saubereren" Weg.

    Das zweite Problem ist ja, dass es beim Client auch einen Fehler verursacht. Dieses WSACall.. sagt mir jetzt nichts und google meint, dass es das eigentlich nicht mehr geben dürfte. Aber ich nehme an, dass du die Verbindung irgendwo mit ClientSocket.Close() schließt. Auch nehme ich mal an, dass du die Empfang-Vorgänge in einem eigenen Thread laufen hast. Beendest du den auch? Also ich mein verhinderst du, dass getMessage() noch mal aufgerufen wird?

    Gruß
    Incognito
    Hey,

    Vielen Dank für die tolle Antwort.

    Klar hab ich den Code:

    VB.NET-Quellcode

    1. Imports System.Net.Sockets
    2. Imports System.Text
    3. Module Multi_ChatServer
    4. Dim clientsList As New Hashtable
    5. Dim fromusername As StringSub Main()
    6. Dim serverSocket As New TcpListener(800)
    7. Dim clientSocket As TcpClient
    8. Dim counter As Integer
    9. serverSocket.Start()
    10. msg("Chat-Server gestartet ....")
    11. msg("Warte auf clients ...")
    12. counter = 0
    13. While (True)
    14. counter += 1
    15. clientSocket = serverSocket.AcceptTcpClient()
    16. Dim bytesFrom(10024) As Byte
    17. Dim dataFromClient As String
    18. Dim networkStream As NetworkStream = clientSocket.GetStream()
    19. networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize))
    20. dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom)
    21. dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"))
    22. clientsList(dataFromClient) = clientSocket
    23. broadcast(dataFromClient + " ist beigetreten ", dataFromClient, False)
    24. broadcast("Nachricht vom Server: Willkommen " + dataFromClient, False, False)
    25. msg(dataFromClient + " ist dem Chatraum beigetreten ")
    26. Dim client As New handleClinet
    27. client.startClient(clientSocket, dataFromClient, clientsList)
    28. End While
    29. clientSocket.Close()
    30. serverSocket.Stop()
    31. msg("exit")
    32. Console.ReadLine()
    33. End SubSub msg(ByVal mesg As String)
    34. mesg.Trim()Console.WriteLine(" >> " + mesg)
    35. End Sub
    36. Private Sub broadcast(ByVal msg As String, ByVal uName As String, ByVal flag As Boolean)
    37. Dim Item As DictionaryEntry
    38. For Each Item In clientsList
    39. Dim broadcastSocket As TcpClient
    40. broadcastSocket = CType(Item.Value, TcpClient)
    41. Dim broadcastStream As NetworkStream = broadcastSocket.GetStream()
    42. Dim broadcastBytes As [Byte]()
    43. If flag = True ThenbroadcastBytes = Encoding.ASCII.GetBytes(uName + " sagt: " + msg)
    44. Else
    45. broadcastBytes = Encoding.ASCII.GetBytes(msg)
    46. End If
    47. broadcastStream.Write(broadcastBytes, 0, broadcastBytes.Length)
    48. broadcastStream.Flush()
    49. Next
    50. End Sub
    51. Public Class handleClinet
    52. Dim clientSocket As TcpClient
    53. Dim clNo As String
    54. Dim clientsList As Hashtable
    55. Public Sub startClient(ByVal inClientSocket As TcpClient, ByVal clineNo As String, ByVal cList As Hashtable)
    56. Me.clientSocket = inClientSocketMe.clNo = clineNo
    57. Me.clientsList = cListDim ctThread As Threading.Thread = New Threading.Thread(AddressOf doChat)
    58. ctThread.Start()
    59. End SubPrivate Sub doChat()
    60. 'Dim infiniteCounter As Integer
    61. Dim requestCount As Integer
    62. Dim bytesFrom(10024) As Byte
    63. Dim dataFromClient As StringDim sendBytes As [Byte]()
    64. Dim serverResponse As String
    65. Dim rCount As String
    66. requestCount = 0
    67. While (True)
    68. Try
    69. requestCount = requestCount + 1
    70. Dim networkStream As NetworkStream = clientSocket.GetStream()
    71. networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize))
    72. dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom)
    73. dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"))
    74. msg("Von client '" + clNo + "': " + dataFromClient)
    75. rCount = Convert.ToString(requestCount)
    76. broadcast(dataFromClient, clNo, True)
    77. Catch ex As Exception
    78. MsgBox(ex.ToString)
    79. End Try
    80. End While
    81. End Sub
    82. End Class
    83. End Module


    Hoffe er hat die Zeilenbrüche richtig gemacht...sonst sry...

    Brauchst du den Client-Code auch noch?

    Ich weiss eben nicht wo und wie genau ich dem Server den Trennen-Befehl geben muss...habe es schon an gewissen Orten mit einfachem .Close()-Code probiert, jedoch gibt es nur Endlosschlaufen (irgendwie...)



    Vielen Dank nochmal für deine Hilfe und ich hoffe der Code nützt...

    Gruss Flash1232
    Dim Item
    und
    For Each Item

    Zwei variablen, gleicher name.
    Würde ich nicht machen.
    Ich glaube, der compiler "verwechselt" Item mit DictionaryEntry^^

    For Each so machen:

    VB.NET-Quellcode

    1. For i As Integer = clientList.Count
    2. Dim broadCastClient As TcpClient = CType(clientList(i).Value, TcpClient) 'Das ist der socket?
    3. Next
    Also, beim Client beendest du die aktuelle Verbindung mit ClientSocket.Close() und beendest alle Prozeduren in denen auf den Server zugegriffen wird bzw. benutzt Try-Catch und verlässt sie im Catch-Block (Exit Sub). Beim Server schreibst du im Catch-Block von doChat() ein client.Close(), nimmst den Client aus deinen Listen raus und verlässt auch die Prozedur.
    In die Broadcast-Sub machst du auch noch einen Catch-Block, in dem du nur die Sub verlässt.

    Das alles geht insgesamt noch "schöner", aber machs mal so, und schau wie viel Fehler du dann noch bekommst.
    Vielen Dank für die Antworten!

    @BeefyX:

    Habe den Code probiert einzufügen, jedoch ist clientList nicht deklariert und Next muss ein entsprechendes For voranstehen. Wie muss ich das also machen?

    @Incognito:

    Gut...habe im Client das so gelöst:

    VB.NET-Quellcode

    1. Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) Handles Me.FormClosing
    2. TryDim outStream As Byte() = System.Text.Encoding.ASCII.GetBytes("Habe den Messenger soeben verlassen." + "$")
    3. serverStream.Write(outStream, 0, outStream.Length)
    4. serverStream.Flush()
    5. Catch
    6. clientSocket.Close()
    7. Exit Sub
    8. End Try
    9. End Sub


    Kann das stimmen? Oder hab ich das am falschen Ort?

    Beim Server hat das client.Close() einen Deklarationsfehler generiert, jedoch 2 Warnungen verdrängt.

    Die Fehlermeldung ändert sich allerdings, wenn ich anstatt client.Close() clientSocket.Close() verwende, zu:





    Man merkt's: Ich bin noch völliger Anfänger und mit einem eigenen Messenger wahrscheinlich noch lange bedient...



    Vielen Dank trotzdem!

    Gruss

    Flash1232

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Flash1232“ ()

    Kein Problem, vielleicht drück ich mich auch einfach nur unklar aus, oder hab was vergessen.

    Äh ja, ich hatte ClientSocket.Close gemeint... Kann es sein, dass du Exit Sub vergessen hast?

    Ich glaub am einfachsten ist es, wenn du dein Projekt mal kurz hochlädst, da kann ich dir glaub ich leichter sagen, wo was hingehört. Sorry, aber das ist dann auch für mich etwas praktischer und keine Angst, ich will nicht abschreiben :P
    Beim schließen muss immer der NetworkStream geschlossen werden...
    Mit TCPClient.Close() erreicht man gar nichts, außer dass das .Net Objekt geschlossen wird, die Verbindung bleibt noch offen...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Jop schon, aber im Normalfall mit Verzögerung und wenn noch Daten ankommen...
    support.microsoft.com/kb/821625/de

    Aber bei einem Chat dürfte das so ziemlich immer der Fall sein...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Mit clientSocket.close() und danach Exit Sub geht zwar die Endlosschlaufe des Fehlers weg jedoch tritt der "Socket nicht verbunden"-Fehler immernoch jeweils 1 mal auf ...

    Also: Hier der Upload des Chat-Servers: Module1.vb

    und hier der Upload des Clients:

    Also hier wäre die Projectfile, jedoch ist die sln-Dateiendung nicht erlaubt? Also hier die grundlegende Form1.vb: Form1.vb

    Falls auch die fertigen Programme erwünscht sind: www.nanowaris.com/downloads/Winnhostat.exe IP-Adresse müsste bei Testverwendung korrigiert werden!

    Und der Server: www.nanowaris.com/downloads/Multi_ChatServer.exe

    Ich garantiere, dass die Dateien sicher und ungefährlich sind!



    Gruss Flash1232

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Flash1232“ ()

    Ööhm...kann es sein, dass es nicht möglich ist, mit meinem Code dem Server die Ausnahmen beizubringen?

    Oder gibt's sonst ein Problem?

    Bin am Verzweifeln mit Try...Catch...End Try :(

    Habe irgendwie das Gefühl, dass in dieser 1. Sub etwas ignoriert wird und dann nicht beendet, sodass die Ausnahme auftritt:

    VB.NET-Quellcode

    1. Sub Main()
    2. Dim serverSocket As New TcpListener(800)
    3. Dim clientSocket As TcpClient
    4. Dim counter As Integer
    5. serverSocket.Start()
    6. msg("Chat-Server gestartet ....")
    7. msg("Warte auf clients ...")
    8. counter = 0
    9. While (True)
    10. counter += 1
    11. clientSocket = serverSocket.AcceptTcpClient()
    12. Dim bytesFrom(10024) As Byte
    13. Dim dataFromClient As String
    14. Dim networkStream As NetworkStream = clientSocket.GetStream()
    15. networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize))
    16. dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom)
    17. dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"))
    18. clientsList(dataFromClient) = clientSocket
    19. broadcast(dataFromClient + " ist beigetreten ", dataFromClient, False)
    20. broadcast("Nachricht vom Server: Willkommen " + dataFromClient, False, False)
    21. msg(dataFromClient + " ist dem Chatraum beigetreten ")
    22. Dim client As New handleClinet
    23. client.startClient(clientSocket, dataFromClient, clientsList)
    24. End While
    25. clientSocket.Close()
    26. serverSocket.Stop()
    27. msg("exit")
    28. Console.ReadLine()
    29. End Sub


    Nach End While wird irgendwie der ganze Code bis zu End Sub ignoriert...daher auch die Warnung "Die clientSocket-Variable wird verwendet, bevor ihr ein Wert zugewiesen wird. Zur Laufzeit kann eine Nullverweisausnahme auftreten.".

    Wie kann ich das berichtigen?



    Flash1232
    Hi, sorry, dass das so lang gedauert hat. Ich hatte letzte Woche nicht so viel Zeit mir das anzuschauen.

    EDIT: Meine Antwort geht jetzt nicht auf deine letzte Frage ein, weil ich sie nicht gelesen hatte. Außerdem hoff ich, dass die hier irgendwo beantwortet wird.

    Also, in deinem Client musst du folgende Subs ausbessern:

    VB.NET-Quellcode

    1. Private Sub msg()
    2. Try
    3. If Me.InvokeRequired Then
    4. Me.Invoke(New MethodInvoker(AddressOf msg))
    5. Else
    6. TextBox1.Text = TextBox1.Text + Environment.NewLine + " >> " + readData
    7. End If
    8. Catch
    9. End Try
    10. End Sub


    VB.NET-Quellcode

    1. Private Sub getMessage()
    2. Try
    3. Do While clientSocket.Connected
    4. For infiniteCounter = 1 To 2
    5. '.....
    6. Next
    7. Loop
    8. Catch
    9. End Try

    VB.NET-Quellcode

    1. Private Sub Form1_Closing
    2. If ClientSocket.Connected Then
    3. Dim outStream As Byte() = System.Text.Encoding.ASCII.GetBytes("Habe den Messenger soeben verlassen." + "$")
    4. serverStream.Write(outStream, 0, outStream.Length)
    5. serverStream.Flush()
    6. serverStream.Close()
    7. End If
    8. End Sub

    Das sollte so klappen, weil der Thread sich ja selbst beendet, wenn er fertig ist.
    Und beim Server:

    VB.NET-Quellcode

    1. Private Sub doChat()
    2. '...
    3. Try
    4. While clientSocket.Connected
    5. requestCount = requestCount + 1
    6. '...
    7. End While
    8. Catch ex As Exception
    9. Finally
    10. Try
    11. clientSocket.Close()
    12. Catch
    13. EndTry
    14. Dim tmpKey As String = ""
    15. For Each Item As DictionaryEntry In clientsList
    16. If Item.Value Is clientSocket Then
    17. tmpKey = Item.Key
    18. Exit For
    19. End If
    20. Next
    21. clientsList.Remove(tmpKey)
    22. End Try
    23. End Sub

    Du solltest aber dafür sorgen, dass jeder Chatbesucher einen Key in der Hastable (also einen Namen) hat, sonst gibt es Probleme.

    Außerdem solltest du dafür sorgen, dass der Client wieder einsatzbereit wird, wenn der Server beendet wird, aber das ist ja was anderes.
    Ich hoffe mal dein Problem ist damit behoben. Bei mir hat jetzt nämlich alles geklappt.

    Gruß Incognito

    PS: Das hier:

    VB.NET-Quellcode

    1. clientSocket.Close()
    2. serverSocket.Stop()
    3. msg("exit")
    4. Console.ReadLine()


    bringt an der Stelle wo du es hast (beim Server in der Main-Sub) nichts, weil es nie aufgerufen wird. Du müsstest irgendwie eine Exit-Funktion einbauen und das dann da aufrufen.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Incognito“ ()

    Vielen Dank!!

    Hallo Incognito,

    Super dein Code! Ich bin dir echt dankbar!

    Alles funktioniert reibungslos!



    Du solltest aber dafür sorgen, dass jeder Chatbesucher einen Key in der Hastable (also einen Namen) hat, sonst gibt es Probleme.


    ööhm wie mache ich das und...was passiert wenn nicht? Gibt es dann bei 2 Gleichnamigen einen Konflikt? oder wenn von 1 PC 2 Clients chatten?

    Dass der Client wieder einsatzbereit ist...das ist doch dass der beim Beenden des Servers nicht einfriert oder?



    Gruss Flash1232 und vielen Dank nochmal!



    PS: Schon ok wegen dem Zeitdruck...das kenn ich! Is ja auch nichts dringendes :P
    Ja, das gibt einen Konflikt. Du kannst zum Beispiel statt einer Hashtable ne normale ArrayList nehmen. Dann hat ja jeder Client eine Nummer, die man auch leicht abrufen kann (IndexOf). Die Namen speicherst du dann in einer zweiten Collection wobei der Client in der einen Liste denselben Index oder Key wie sein Name in der anderen Liste hat.
    Aber vielleicht fällt dir ja auch was ein, was sich in deine momentane Lösung leichter integrieren lässt.

    Zwei Clients an einem PC sind kein Problem.

    Mit wieder einsatzbereit hab ich bloß gemeint, dass du ihn wieder so einstellst, als ob er gerade erst gestartet währe, damit man sich gleich wieder verbinden könnte, aber das ist ja nur ein kleiner "Schönheitsfehler" ;)
    Ööhm dann muss ich dem Server sagen, er soll jedem Client eine Nummer geben oder wie jetzt?

    also ist die nummer dann dem Namen zugeordnet oder?

    Ich bin eben da noch sehr unerfahren ...

    Tut mir leid wenns so tönt als versuche ich ein 1x1 mit einem Raketenstart zu verwechseln.

    Ja der Schönheitsfehler ist nich so wichtig ;)