Privater Chat (Server-Client)

  • VB.NET

Es gibt 23 Antworten in diesem Thema. Der letzte Beitrag () ist von rausch88.

    Privater Chat (Server-Client)

    Guten Tag!
    Ich habe mir nun etliche Chat-Tutorials angesehen, aber leider ist bei jedem "nur" ein Multiuser-Chat möglich. Ich habe mein Chatprogramm nun in ein einziges P2P-Programm umgewandelt (Client - Client), aber nun muss jeder die Ports freischalten (unangenehm für die Benutzer).
    Ich will das ganze nun also wieder mit einem fixen Server lösen, das einzige Problem ist, dass ich einen privaten Chat haben will, keinen öffentlichen.
    Beim Client sollte das so aussehen: Er bekommt eine Liste mit allen Benutzern, die online sind (also alle Clients, die mit dem Server verbunden sind), klickt einen von diesen an und kann einen privaten Chat mit diesem starten. Ideal wäre, wenn er mehrere private Chats zugleich führen könnte (Client - Server - Client).
    Weiß jemand zufällig ein Tutorial, welches näher auf dieses Thema eingeht?
    MfG und vielen Dank im Voraus,
    Markus

    PS: bigspeed.net/index.php?page=bsp2psdk wäre perfekt, nur leider kostet das 300$.
    In der Fachsprache nennt man Dein Vorhaben nicht Chat sondern Messenger xD
    Ich weiß nicht genau wo Dein Problem ist, vor allen Dingen wenn Du schon sowas wie einen Multiuserchat hinbekommen hast.
    Anstatt allen alle Nachrichten zu schicken, verteilst Du dann einfach unter den miteinander verbundenen Clients die Nachrichten. Woher Du weißt wer mit wem "verbunden" ist, wenn er es de Facto nicht direkt ist? Wie wärs mit einem eigenen Protokoll. Statt nur Nachrichten zu verschicken, kannst Du einen Header schicken, in dem Sender, Empfänger und vielleicht auch noch andere Dinge hinterlegt sind. Anhand dieser Informationen kann der Server dann entscheiden, wem was zu schicken ist. Und über das Protokoll kannst Du dann noch viel mehr Abschicken (Versand von Dateien, Austausch von Profilinformationen..etc.).

    Gruß FatFire

    PS: Vernünftiges Tut hab ich jetzt allerdings auch nicht gefunden.
    Danke für deine Idee, ich versuche nun, Nachrichten mit einem String wie "chatto:usera" zu verschicken, sodass der andere Client (usera) weiß, dass der Chat für ihn bestimmt ist.
    Ich hab mich nach diesem Tutorials orientiert: [VB.NET] Multiserver (TCP)
    Gibt es hierbei die Möglichkeit, dass der Server alle paar Sekunden eine Liste an alle Clients sendet, die die Namen der verbundenen Clients anzeigt?
    Ich habs mit

    VB.NET-Quellcode

    1. For Each c As Connection In list
    2. MsgBox(c)
    3. Next

    versucht, aber ich weiß nicht genau, wo ich das einfügen muss und ob das überhaupt klappt (das MsgBox ersetze ich danach natürlich durch ein streamw.WriteLine).

    MfG
    Markus
    Ich würde an Deiner Stelle erst einmal noch die Programmiererei lassen und mir Gedanken um ein Protokoll machen. Dabei als erstes eine (möglichst komplette) Liste aller Informationen, die jemals vom Client zum Server und umgekehrt gesendet werden müssen.

    So wirst Du dann z.B. recht schnell sehen, dass sich das mit der Liste auch viel besser im Rahmen des Protokolls abhandeln lässt.
    Im Header dann z.B. die Nachricht "userlist" und im Informationsteil die Namen der User.

    Wahrscheinlich wirst Du dann noch ein paar mehr Sachen finden, die Du unbedingt implementiert haben möchtest. Sowas baut man dann am besten alles auf einmal ein und nicht nach und nach immer was ranstückeln.

    Um die Liste zu verteilen könntest Du z.B. einen zusätzlichen Thread aufmachen, der in festen Intervallen (nicht zu kurz, denn die Arbeit steigt mit zunehmender Teilnehmerzahl doch beträchtlich an, also vielleicht alle 10-20 Sekunden oder noch länger) den Text der Liste zusammenstellt und dann über SendToAllClients an alle verteilt.

    Da kommt mir gerade was: Du verteilst jetzt aber nicht alle Nachrichten die reinkommen an alle, um die dann beim Client nur die für ihn bestimmten rausfiltern zu lassen, oder?

    Gruß FatFire
    Man braucht ja nicht jedesmal eine komplette Liste aller clients schicken.
    Bei der Anmeldung erhält der client die komplette Liste und danach nur noch die Änderungen. Nach dem Motto:
    +Peter;+Mary;-Paul;*John
    d.h. Peter und Mary sind neu, Paul hat sich abgemeldet und John ist "away" aber verbunden.
    Stimmt, das wäre natürlich noch besser, weil Du dann die Änderungen auch übermitteln könntest, wenn diese eintreten (denn Du kriegst ja mit, wann sich wer verbindet und wann er sich trennt vom Chat).
    Und genau wegen sowas lohnt es sich, vor dem Coden erst einmal nachzudenken, wie man das am besten machen kann (ich hab mir da vorhin nicht so viel Mühe gegeben, ich gebe es ja zu :) ).

    Gruß FatFire
    @FatFire: Doch, ich versende derzeit die Nachricht an alle, und jeder Client filtert sich die für ihn bestimmte Nachricht heraus. Sonst muss ich es ja am Server filtern, da kann ichs gleich beim Client machen...
    Was meinst du mit einem Protokoll mit mehreren Informationen? Man kann bei TCP doch nur einen String übermitteln. Sogesehen müssten sich doch alle Informationen in der Nachricht befinden?
    MfG
    Markus

    Afritus schrieb:

    Man kann bei TCP doch nur einen String übermitteln

    Nein. Über TCP kannst du alles mögliche übermitteln. Abgesehen davon kann man in einen String auch diverse Infos packen.

    Wenn du alles immer an alle schickst, auch wenn es die nix angeht, ist das unschön. Es sollte jeder client nur genau das bekommen, was auch tatsächlich für ihn ist.
    @FatFire: Doch, ich versende derzeit die Nachricht an alle, und jeder Client filtert sich die für ihn bestimmte Nachricht heraus. Sonst muss ich es ja am Server filtern, da kann ichs gleich beim Client machen...

    Uh, das ist schlecht. So kann jeder alle Gespräche die auf dem Server geführt werden mitschneiden. Er muss nur den einkommenden Datenstrom abhören oder einen modifizierten Client nutzen. Filtere auf dem Server und schicke Nachrichten nur an die, die sie benötigen. Weiterer Vorteil: Du sparst viel Bandbreite.
    Was meinst du mit einem Protokoll mit mehreren Informationen? Man kann bei TCP doch nur einen String übermitteln. Sogesehen müssten sich doch alle Informationen in der Nachricht befinden?

    Du trennst eine Nachricht in Header- und Datenbereich. Im Header stehen Verwaltungsinformationen (Vorgangskennung, von wem, für wen, eventuell noch Sachen wie Uhrzeit und und und...). Und im Datenbereich eben die Daten (z.B. die Nachricht des Benutzers oder die Liste der Benutzer, je nach Vorgangskennung!).
    Um das mit dem "ich kann doch nur einen String schicken" zu lösen, gibt es zwei Möglichkeiten: Header fixer Länge, d.h. jedes Feld hat eine feste Länge. Nachteil: eventuell bleiben viele Bereiche leer und ich verschwende Bandbreite.
    Andere Möglichkeit: ich gebe im Header einfach mit an, wie groß mein Header ist. Dann muss der Client die Bereiche an der Stelle eben auftrennen. ABER: die Position der Größenangabe muss schon an einer fixen Position sein ;)

    Gruß FatFire
    Ich muss zugeben, ich habe zum Großteil deshalb beim Client gefiltert, weil es mir da leichter vorkam. Ich werde nun versuchen, am Server zu filtern...
    Was genau meinst du mit Header? Ist der Header fester Bestandteil der Nachricht? Wie kann ich z.B. definieren, wo der Benutzername des Senders/Empfängers steht und wo die Nachricht steht? Es muss sich ja alles in einem String befinden, oder?
    MfG
    Markus
    Danke für den Link, das schafft schon mehr Klarheit.
    Der Header muss also der eigentlichen Nachricht vorangestellt werden. Und im Header befinden sich alle Informationen. Und dennoch bleibt ein Problem: Der Header ist doch eigentlich auch eine Nachricht, wie soll der Server/Client zwischen einer normalen Nachricht und einem Header unterscheiden können?

    Ich habe mir das bisher nur so vorgestellt:
    1. Auf der Server befindet sich eine ListBox mit allen angemeldeten Usern.
    2. Der Server erhält von irgendeinem User eine Nachricht mit dem Inhalt "chatto:A" und "chatfrom:B".
    3. Der Server kennt diese Befehle und weiß, dass die Nachricht von B kommt und er sie an A weiterschicken soll (A und B befinden sich in der ListBox, somit weiß der Server, nach welchen Namen er in der Nachricht suchen muss).
    Nun ergeben sich mehrere Probleme:
    a. Wie weiß der Server, dass "chatto:A" ein Befehl und kein Bestandteil der Nachricht ist?
    b. Wie kann ich an einen einzelnen User senden (ich kenne bisher nur SendToAllClients)?
    c. Nachdem A nun die Nachricht von B empfangen hat, muss A wissen, dass die Nachricht von B kommt (um zurückschreiben zu können). Also das "chatto" und "chatfrom" noch mitschicken, der Client muss das dann umtauschen?

    Vielen Dank für die Hilfe,
    Markus

    Afritus schrieb:

    Der Header ist doch eigentlich auch eine Nachricht, wie soll der Server/Client zwischen einer normalen Nachricht und einem Header unterscheiden können?

    XML-Quellcode

    1. <header><sender>John</sender><receiver Mary /><receiver Paul /></header><body>bla bla bla</body>

    zb, denn Möglichkeiten gibts dafür wie Sand am Meer
    a. Wie weiß der Server, dass "chatto:A" ein Befehl und kein Bestandteil der Nachricht ist?

    Jeder Nachricht muss ein Header vorangestellt werden. Und die Form des Header (Anzahl der Felder, Größe der Felder, Bedeutung der Felder) muss klar von Dir definiert und jederzeit interpretierbar sein.
    b. Wie kann ich an einen einzelnen User senden (ich kenne bisher nur SendToAllClients)?

    Du musst passend zu den Connections eine Liste führen, in der die Nicks der User stehen. Beim Senden an eine einzelne Person Empfänger-Nick in der Liste suchen und Nachricht über die zugehörige Connection schicken.
    c. Nachdem A nun die Nachricht von B empfangen hat, muss A wissen, dass die Nachricht von B kommt (um zurückschreiben zu können). Also das "chatto" und "chatfrom" noch mitschicken, der Client muss das dann umtauschen?

    Joh.

    Gruß FatFire
    @Picoflop: Ist das nicht html? Wie kann ich das in VB.NET einfügen? Einfach mit dem String übermitteln, das Programm erkennt das dann?

    @FatFire: Für eine Auflistung verwendet mein Server ja das hier:

    VB.NET-Quellcode

    1. For Each c As Connection In list
    2. Try
    3. c.streamw.WriteLine(s)
    4. c.streamw.Flush()
    5. Catch
    6. End Try
    7. Next

    Wie is da der Befehl für das Senden an eine bestimmte Connection?
    In etwa so?

    VB.NET-Quellcode

    1. For Each c As Connection In list
    2. If c = "Peter" Then 'Name des Clients
    3. c.streamw.WriteLine(s)
    4. End If
    5. Next


    MfG
    @Picoflop: Ist das nicht html? Wie kann ich das in VB.NET einfügen? Einfach mit dem String übermitteln, das Programm erkennt das dann?

    Nein, XML. Das erlaubt einem als offener Standard viele Möglichkeiten der Verarbeitung, verursacht allerdings auch viel Overhead...musst Du entscheiden, ob es für Dich das richtige ist.

    Wenn dann:

    VB.NET-Quellcode

    1. For Each c As Connection In list
    2. If c.[b]nick[/b] = "Peter" Then 'Name des Clients
    3. c.streamw.WriteLine(s)
    4. End If
    5. Next

    Vorausgesetzt natürlich, es ist Peter, an den die Nachricht gehen soll.

    Gruß FatFire

    Edit: @picoflop: aber so ganz in Ordnung ist das als XML auch nicht...sollen die receiver jetzt Attribute oder Elementinhalte sein? Vielleicht eher

    XML-Quellcode

    1. <header><sender>John</sender><receiver nick="Mary" /><receiver nick="Paul" /></header><body>bla bla bla</body>
    ?
    Oh, der XML-Parser hier erkennt ja gar keine Empty-Element-Tags...

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

    FatFire schrieb:

    ber so ganz in Ordnung ist das als XML auch nicht

    Durchaus möglich ;) Das war jetzt auch nur als "ungefähr so" gedacht, um zu verdeutlichen, wie man es machen könnte. Das mit dem Overhead ist richtig, ich persönlich würde das ganze binär machen, aber aufgrund der Fragestellung scheint das für den Frager ggfs etwas zu kompliziert zu sein.

    00:4 HeaderLength
    04:1 HeaderVersion
    05:1 PacketType
    .... Header Data
    HeaderLength:4 DataLength
    HeaderLength+4:1 DataType
    .... Body Data

    etc. So bleibt man ggfs kompatibel wenn unterschiedliche client-versionen verwendet werden und man kann im Data-Bereich dann halt Binärdaten, Texte etc schicken
    Jetzt kann man natürlich auswürfeln was leichter zu implementieren:
    -Binärcodierung, wobei Texte im passenden Encoding umgesetzt werden müssen und eine sehr strikte Protokolldefinition unabdingbar ist.
    -Textcodierung, wobei das Encoding festgelegt sein muss (utf8 bietet sich ja an), leichter nachvollziehbar, aber spätestens beim Schicken von Binärdaten auch keine wirkliche Freude aufkommen will (Base64 erklären kann lustig werden...).

    Persönlich würde ich 1. auch bevorzugen...aber erklären mag ich es nicht :D

    Gruß FatFire
    Sooo, stop mal. Bin vor ungefähr 3 Beiträgen ausgestiegen :D
    Also nochmal:
    Ich verwende nun zum Senden an einen einzelnen Client folgendes:

    VB.NET-Quellcode

    1. Private Sub SendToOneClient(ByVal t As String, ByVal s As String)
    2. For Each c As Connection In list ' an alle clients weitersenden.
    3. Try
    4. If c.nick = t Then
    5. c.streamw.WriteLine(s)
    6. c.streamw.Flush()
    7. End If
    8. Catch
    9. End Try
    10. Next
    11. End Sub

    Aufgerufen mit:

    VB.NET-Quellcode

    1. SendToOneClient("Peter", "hallo")


    Ok, gut. Mit Binärcodierung etc. will ich nun nicht anfangen, da ich in etwa 0 Plan davon habe.
    Den Overhead mit XML nehme ich erstmal in Kauf. Habe nun etwas im Internet gelesen, jedoch weiß ich nicht, wie ich XML und TCP miteinander kombinieren kann.
    Das wird wohl kaum funktionieren:

    VB.NET-Quellcode

    1. SendToOneClient("Peter", "<header><sender>Mark</sender><receiver>Peter</receiver></header><body>hallo</body>")

    Zudem muss der Client nun die einzelnen Informationen aus "sender", "receiver" und "body" auslesen können. Hier stehe ich komplett auf der Leitung... Kann eventuell jemand ein kurzes Beispiel zeigen, wie der Text "hallo" vom Sender "Mark" an den Empfänger "Peter" versendet werden kann (in VB.NET und TCP)?
    MfG
    Also hier z.B. eine Methode, die einem das XML als Text zurückliefert. Quelle, mehrere Ziele als Array und Inhalt der Nachricht müssen angegeben werden:

    VB.NET-Quellcode

    1. Private Function CreateMessage(ByVal Source As String, _
    2. ByVal Destinations As String(), _
    3. ByVal Content As String) As String
    4. Dim XmlDoc As New XmlDocument
    5. Dim MessageNode As XmlNode = XmlDoc.CreateElement("message")
    6. XmlDoc.AppendChild(MessageNode)
    7. Dim HeaderNode As XmlNode = XmlDoc.CreateElement("header")
    8. MessageNode.AppendChild(HeaderNode)
    9. Dim SenderNode As XmlNode = XmlDoc.CreateElement("sender")
    10. SenderNode.InnerText = Source
    11. HeaderNode.AppendChild(SenderNode)
    12. Dim ReceiverNode As XmlNode
    13. For Each Destination As String In Destinations
    14. ReceiverNode = XmlDoc.CreateElement("receiver")
    15. ReceiverNode.InnerText = Destination
    16. HeaderNode.AppendChild(ReceiverNode)
    17. Next
    18. Dim BodyNode As XmlNode = XmlDoc.CreateElement("body")
    19. BodyNode.InnerText = Content
    20. MessageNode.AppendChild(BodyNode)
    21. Return XmlDoc.OuterXml
    22. End Function

    Aufruf sieht dann z.B. so aus:

    VB.NET-Quellcode

    1. ZuSendenderText = CreateMessage("Mark", New String() {"Peter"}, "<Hallo!>")

    Um sich z.B. jetzt alle Empfänger auf der Konsole ausgeben zu lassen, z.B. wie folgt:

    VB.NET-Quellcode

    1. Dim Receivers As XmlNodeList = XmlDoc.GetElementsByTagName("receiver")
    2. For Each Receiver As XmlNode In Receivers
    3. Console.WriteLine(Receiver.InnerText)
    4. Next

    Gruß FatFire