WPF TCP Übertragungsproblem

  • VB.NET
  • .NET (FX) 4.0

Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von Vainamo V.

    WPF TCP Übertragungsproblem

    Hi erstmal.

    Ich will zur Zeit meinen Chat in WPF verwirklichen. Für den Datenverkehr zwischen Server und Client habe ich bisher folgende Klasse erstellt:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Net.Sockets
    2. Imports System.IO
    3. Imports System.Security.Cryptography
    4. Public Class Prismaes
    5. Dim Client As New TcpClient
    6. Dim Stream As NetworkStream
    7. Dim StreamW As StreamWriter
    8. Dim StreamR As StreamReader
    9. Dim SHA1 As New SHA1CryptoServiceProvider
    10. Public Event ConnectionFailed(ByVal e As ConnectionFailedEventArgs)
    11. Public Event IncomingMessage(ByVal e As NewMessageEventArgs)
    12. Public Event Connected()
    13. Public ThrdConnection As New System.Threading.Thread(AddressOf ListenToStream)
    14. Sub New()
    15. End Sub
    16. Sub Connect(ByVal ServerAddress As String, ByVal ServerPort As Integer, ByVal SPId As String, ByVal Password As String)
    17. Try
    18. Client.Connect(ServerAddress, ServerPort)
    19. Stream = Client.GetStream
    20. StreamW = New StreamWriter(Stream)
    21. StreamR = New StreamReader(Stream)
    22. ThrdConnection.Start()
    23. Dim CryptPassword As String = BitConverter.ToString(SHA1.ComputeHash(System.Text.Encoding.Default.GetBytes(Password)))
    24. Send(SPId & ":" & CryptPassword & ":8.1") '& Version
    25. RaiseEvent Connected()
    26. Catch ex As Exception
    27. RaiseEvent ConnectionFailed(New ConnectionFailedEventArgs With {.FailureReason = ConnectionFailedEventArgs.FailureReasons.ServerNotReachable})
    28. End Try
    29. End Sub
    30. Sub Send(ByVal ToSend As String)
    31. StreamW.Write(ToSend)
    32. StreamW.Flush()
    33. End Sub
    34. Sub ListenToStream()
    35. While Client.Connected
    36. RaiseEvent IncomingMessage(New NewMessageEventArgs With {.Message = StreamR.ReadLine})
    37. End While
    38. End Sub
    39. End Class
    40. Public Class ConnectionFailedEventArgs
    41. Enum FailureReasons
    42. ServerNotReachable = 0
    43. DataIncorrect = 1
    44. End Enum
    45. Public Property FailureReason As FailureReasons
    46. End Class
    47. Public Class NewMessageEventArgs
    48. Public Property Message As String
    49. End Class


    Der Client selbst hat dann diesen Code:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public WithEvents Prismae As New Prismaes
    2. Public Delegate Sub DSHandleMessage(ByVal MessageToHandle As String)
    3. Sub Connected() Handles Prismae.Connected
    4. fyoLogon.IsOpen = False
    5. End Sub
    6. Sub ConnectionFailed(ByVal e As ConnectionFailedEventArgs) Handles Prismae.ConnectionFailed
    7. Select Case e.FailureReason
    8. Case ConnectionFailedEventArgs.FailureReasons.ServerNotReachable
    9. Me.ShowMessageAsync("Verbindung fehlgeschlagen", "Es konnte keine Verbindung mit dem angegebenen Server hergestellt werden. Überprüfen sie die IP-Adresse und stellen sie sicher, dass der Server erreichbar ist.", MessageDialogStyle.Affirmative)
    10. End Select
    11. End Sub
    12. Sub NewMessageIncoming(ByVal e As NewMessageEventArgs) Handles Prismae.IncomingMessage
    13. Dispatcher.Invoke(New DSHandleMessage(AddressOf HandleMessages), e.Message)
    14. End Sub
    15. Sub HandleMessages(ByVal Message As String)
    16. If Message = "/v/success" Then
    17. isLoggedIn = True
    18. ElseIf Message.StartsWith("/w") Then
    19. dtaUsername = Message.Remove(0, 3).Split(" "c)(3)
    20. btnLogon.Content = "Hallo " & dtaUsername
    21. End If
    22. End Sub


    Es scheint nun als würde sich der Client verbinden, allerdings meldet der Server nicht, dass der Client sich verbunden hat (Der Server funktioniert aber 100%-ig richtig) und der Client ändert die Variable dtaUsername nicht.

    Ich vermute dass es irgendwo bei der Kommunikation zwischen der Klasse und dem Client einen Fehler gibt, allerdings ist habe ich es vorher ohne extra Klasse gemacht, sodass ich mir da nicht sicher bin.

    Wenn jemand den Fehler sieht und mir helfen kann würde mich das sehr freuen :love:

    ~Grüße Väinämö
    Hi,
    ich schreibe auch momentan einen Chat (in WPF) und kann dir mal ein paar Tipps geben:Ich habe es dann folgendermaßen gemacht: Es gibt eine (wie auch in EDRs Chat) Klassenbibliothek dazwischen, welche die Austauschinformationen bereitstellt. Dann gibt es zwei Enums: SendingType und ResponseType, beide vom Typ Byte:

    C#-Quellcode

    1. public enum ResponseType : byte
    2. {
    3. /// <summary>
    4. /// There was an error. The error type is the next byte
    5. /// </summary>
    6. Error,
    7. /// <summary>
    8. /// Gets a text message. Structure: 16 bytes the guid of the receiver and then the message, encoded as utf8
    9. /// </summary>
    10. Message,
    11. /// <summary>
    12. /// The message was send successul
    13. /// </summary>
    14. MessageSendSuccessful
    15. //....
    16. public enum SendingType : byte
    17. {
    18. /// <summary>
    19. /// Everything is okay
    20. /// </summary>
    21. IAMREADY = 5,
    22. /// <summary>
    23. /// Sends a text message. Structure: 16 bytes the guid of the receiver and then the message, encoded as utf8
    24. /// </summary>
    25. MessageToReceiver,
    26. /// <summary>
    27. /// Sends a friend request. The next 16 bytes are the guid of the friend
    28. /// </summary>
    29. SendFriendRequest
    30. //....

    Ich habe dann im Gegensatz zu @ErfinderDesRades immer einen Byte gelesen:

    C#-Quellcode

    1. private readonly Func<byte> _readByteDelegate;
    2. public TcpClient(System.Net.Sockets.TcpClient client, BinaryReader reader, BinaryWriter writer)
    3. {
    4. //....
    5. _readByteDelegate += BinaryReader.ReadByte;
    6. _readByteDelegate.BeginInvoke(EndRead, null);
    7. }
    8. private void EndRead(IAsyncResult asyncResult)
    9. {
    10. try
    11. {
    12. var parameter = _readByteDelegate.EndInvoke(asyncResult);
    13. var size = BinaryReader.ReadInt32();
    14. byte[] bytes = null;
    15. if(size > 0) bytes = BinaryReader.ReadBytes(size);
    16. OnReceived(this, new ReceivedEventArgs(parameter, bytes));
    17. _readByteDelegate.BeginInvoke(EndRead, null);
    18. }
    19. catch (Exception)
    20. {
    21. Dispose();
    22. }
    23. }


    Das bedeutet, dass ein Paket so aufgebaut ist: Als erstes Byte der Parameter, der dann direkt zu dem Enum gecastet werden kann. Als nächstes wird ein Integer gelesen, der festlegt, wie viele weitere Bytes das Paket hat. Diese werden dann auch gelesen und mitsamt dem Parameter als Byte Array dem Event übergeben. Dann kann man einfach:

    VB.NET-Quellcode

    1. ​Select Case DirectCast(e.Parameter, SendingType)

    machen. Angenommen, du willst eine Nachricht verschicken. Dann fügst du deinem Enum SendingType einfach den Eintrag ​MessageToReceiver hinzu. Der Client sendet dann zuerst diesen Enum Type als Byte und dann alles, was für die Nachricht gebraucht wird. Das kommt dann beim Server an, der erkennt, wohin dein Client die Nachricht verschicken will (ich habe es so gemacht, dass jeder Client eine 16 Byte GUID hat, welche dann die ersten 16 Bytes des Paketes sind) und leitet sie weiter.
    So sieht das dann konkret bei mir auf dem Server aus:

    C#-Quellcode

    1. switch ((SendingType)e.Parameter)
    2. {
    3. case SendingType.MessageToReceiver:
    4. Connection receiverConnection;
    5. Guid receiverGuid;
    6. Guid messageGuid;
    7. try
    8. {
    9. receiverGuid = new Guid(e.Data.Take(16).ToArray());
    10. }
    11. catch (Exception)
    12. {
    13. Trace.WriteLine(
    14. $"[ERROR] Invalid message from {sendingConnection.Account.Name}: Invalid receiver guid format");
    15. return;
    16. }
    17. try
    18. {
    19. receiverConnection = _server.Connections.First(x => x.Account.Id == receiverGuid);
    20. }
    21. catch (ArgumentException)
    22. {
    23. Trace.WriteLine(
    24. $"[ERROR] Invalid message from {sendingConnection.Account.Name} to {receiverGuid}: Receiver not found");
    25. return;
    26. }
    27. if (!_database.Friendships.Any(x => x.User1 == sendingConnection.Account.Id || x.User1 == receiverGuid))
    28. {
    29. sendingConnection.TcpClient.Send(ResponseType.Error,
    30. new[] {(byte) ErrorType.ReceiverIsNotYourFriend});
    31. break;
    32. }
    33. try
    34. {
    35. messageGuid = new Guid(e.Data.Skip(16).Take(16).ToArray());
    36. }
    37. catch (Exception)
    38. {
    39. Trace.WriteLine(
    40. $"[ERROR] Invalid message from {sendingConnection.Account.Name} to {receiverGuid}: Invalid message guid format");
    41. return;
    42. }
    43. var package = new List<byte>();
    44. package.AddRange(sendingConnection.Account.Id.ToByteArray());
    45. package.AddRange(e.Data.Skip(32).ToArray());
    46. receiverConnection.TcpClient.Send(ResponseType.Message, package.ToArray());
    47. var backPackage = new List<byte>();
    48. backPackage.AddRange(receiverGuid.ToByteArray());
    49. backPackage.AddRange(messageGuid.ToByteArray());
    50. sendingConnection.TcpClient.Send(ResponseType.MessageSendSuccessful, backPackage.ToArray());
    51. break;


    Und so bei dem Client:

    C#-Quellcode

    1. private void SendMessage(Profil friend, Message message)
    2. {
    3. TcpClient.Send((byte)SendingType.MessageToReceiver);
    4. var sendBytes = new List<byte>();
    5. sendBytes.AddRange(friend.Id.ToByteArray());
    6. sendBytes.AddRange(message.Id.ToByteArray());
    7. sendBytes.AddRange(Encoding.UTF8.GetBytes(message.Content));
    8. var sendArray = sendBytes.ToArray();
    9. TcpClient.Send(sendArray.Length);
    10. TcpClient.Send(sendArray);
    11. }


    So hast du dann eine stabile Basis und kannst auch so Features einbauen wie anzeigen, ob der gegenüber gerade schreibt, sein Profilbild ändert, Offline geht, dir eine Freundschaftsanfrage schickt/annimmt/ablehnt, ...
    Mfg
    Vincent

    @VincentTB danke dafür. Allerdings habe ich ja schon ein fertiges und funktionieredes Kommunikationssystem (siehe hier: SkrivaPrisma 8 - TCP Chat)

    Das Problem vor dem ich zur Zeit stehe ist die Kommunikation zwischen der Klasse und dem Window. Ich vermute dass der Fehler irgendwo zwischen dem Thread (Spoiler 1 #43-#47), dem Event-Handle (Spoiler 2 #15-#17) und dem Invoke liegt, da ich mich damit einfach nicht auskenne.

    BTT: Die Frage ist was ist der Fehler? Ist vielleicht der Invoke falsch?