Multiserver (TCP)

    • VB.NET

    Es gibt 854 Antworten in diesem Thema. Der letzte Beitrag () ist von ClonkAndre.

      Multiserver (TCP)

      Hi,

      ich habe mal ein einfaches Beispiel - oder eher eine Grundlage - für einen stabilen TCP-MultiServer programmiert, jeder Client bekommt dabei einen eigenen Thread zugewiesen. Hoffentlich lehrreich und ausreichend kommentiert.

      Download: MultiServer Sample.7z

      Im folgenden der Code noch einmal direkt gepostet.

      Der MultiServer (Konsolenanwendung)

      VB.NET-Quellcode

      1. Imports System.Net.Sockets
      2. Imports System.IO
      3. Imports System.Net
      4. ' TCP-MultiServer
      5. ' C 2009 - Vincent Casser
      6. Module Module1
      7. Private server As TcpListener
      8. Private client As New TcpClient
      9. Private ipendpoint As IPEndPoint = New IPEndPoint(IPAddress.Any, 8000) ' eingestellt ist port 8000. dieser muss ggf. freigegeben sein!
      10. Private list As New List(Of Connection)
      11. Private Structure Connection
      12. Dim stream As NetworkStream
      13. Dim streamw As StreamWriter
      14. Dim streamr As StreamReader
      15. Dim nick As String ' natürlich optional, aber für die identifikation des clients empfehlenswert.
      16. End Structure
      17. Private Sub SendToAllClients(ByVal s As String)
      18. For Each c As Connection In list ' an alle clients weitersenden.
      19. Try
      20. c.streamw.WriteLine(s)
      21. c.streamw.Flush()
      22. Catch
      23. End Try
      24. Next
      25. End Sub
      26. Sub Main()
      27. Console.WriteLine("Der Server läuft!")
      28. server = New TcpListener(ipendpoint)
      29. server.Start()
      30. While True ' wir warten auf eine neue verbindung...
      31. client = server.AcceptTcpClient
      32. Dim c As New Connection ' und erstellen für die neue verbindung eine neue connection...
      33. c.stream = client.GetStream
      34. c.streamr = New StreamReader(c.stream)
      35. c.streamw = New StreamWriter(c.stream)
      36. c.nick = c.streamr.ReadLine ' falls das mit dem nick nicht gewünscht, auch diese zeile entfernen.
      37. list.Add(c) ' und fügen sie der liste der clients hinzu.
      38. Console.WriteLine(c.nick & " has joined.")
      39. ' falls alle anderen das auch lesen sollen können, an alle clients weiterleiten. siehe SendToAllClients
      40. Dim t As New Threading.Thread(AddressOf ListenToConnection)
      41. t.Start(c)
      42. End While
      43. End Sub
      44. Private Sub ListenToConnection(ByVal con As Connection)
      45. Do
      46. Try
      47. Dim tmp As String = con.streamr.ReadLine ' warten, bis etwas empfangen wird...
      48. Console.WriteLine(con.nick & ": " & tmp)
      49. SendToAllClients(con.nick & ": " & tmp) ' an alle clients weitersenden.
      50. Catch ' die aktuelle überwachte verbindung hat sich wohl verabschiedet.
      51. list.Remove(con)
      52. Console.WriteLine(con.nick & " has exit.")
      53. Exit Do
      54. End Try
      55. Loop
      56. End Sub
      57. End Module


      Ein einfacher Beispiel-Client (Windows-Forms-Anwendung)

      - Eine Listbox für die Auflistung der Nachrichten.
      - Eine Textbox für die Nachricht
      - Ein Button für's absenden.

      VB.NET-Quellcode

      1. Imports System.Net.Sockets
      2. Imports System.IO
      3. Public Class Form1
      4. Private stream As NetworkStream
      5. Private streamw As StreamWriter
      6. Private streamr As StreamReader
      7. Private client As New TcpClient
      8. Private t As New Threading.Thread(AddressOf Listen)
      9. Private Delegate Sub DAddItem(ByVal s As String)
      10. Private nick As String = "unknown"
      11. Private Sub AddItem(ByVal s As String)
      12. ListBox1.Items.Add(s)
      13. End Sub
      14. Private Sub Form1_Shown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shown
      15. Try
      16. client.Connect("127.0.0.1", 8000) ' hier die ip des servers eintragen.
      17. ' da dieser beim testen wohl lokal läuft, hier die loopback-ip 127.0.0.1.
      18. If client.Connected Then
      19. stream = client.GetStream
      20. streamw = New StreamWriter(stream)
      21. streamr = New StreamReader(stream)
      22. streamw.WriteLine(nick) ' das ist optional.
      23. streamw.Flush()
      24. t.Start()
      25. Else
      26. MessageBox.Show("Verbindung zum Server nicht möglich!")
      27. Application.Exit()
      28. End If
      29. Catch ex As Exception
      30. MessageBox.Show("Verbindung zum Server nicht möglich!")
      31. Application.Exit()
      32. End Try
      33. End Sub
      34. Private Sub Listen()
      35. While client.Connected
      36. Try
      37. Me.Invoke(New DAddItem(AddressOf AddItem), streamr.ReadLine)
      38. Catch
      39. MessageBox.Show("Verbindung zum Server nicht möglich!")
      40. Application.Exit()
      41. End Try
      42. End While
      43. End Sub
      44. Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
      45. streamw.WriteLine(TextBox1.Text)
      46. streamw.Flush()
      47. TextBox1.Clear()
      48. End Sub
      49. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      50. nick = InputBox("Nickname: ", "Namen festlegen", "unknown")
      51. End Sub
      52. End Class


      Mit dem MultiServer-Code als Vorlage lässt sich schon einiges anstellen. Der Server muss natürlich immer laufen. Viel Spaß dabei, bitte kein reines C&P betreiben sondern den Code ansehen und verstehen (!), bei Fragen antworten.

      Den Code bitte ohne Nachfrage nicht anderweitig publizieren.

      LG

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „kevin89“ ()

      Finde ich gut, dass du jetzt auch einen Multi-Server-Tutorial gemacht hast :) Werde es späer ausprobieren,
      kann man dafür auch den Client aus dem anderen tutorial verwenden?

      // Edit :
      Klappt prima, muss nur noch im Client ein zusätzliches Feld für den Nick einfügen :)

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

      Nein :)

      Der Name "MessageBox" wurde nicht deklariert.
      Der Name "Application" wurde nicht deklariert.
      Ersteinmal Dankeschön für dieses schöne Beispiel eines MultiUserChats. Hab da allerdings ein paar Fragen: Ich selber möchte auch einen Chat basteln, jedoch einen wo der Server nicht als Konsole sondern auch als Windows Form Anwendung läuft. Jetzt hab ich da ja das Problem mit der Endlosschleife, dass ich keine anderen Benutzereingaben mehr abfangen kann. Wäre es nicht grundsätzlich auch besser (sorry wenn ich jetzt was erfrage was es nicht gibt ;-)) einen onEvent abzufangen?

      Wenn es diesen gibt, frag ich mich als kleiner Hobbyprogrammierer wo ich das bei den Sockets finde...

      Viele Grüße
      Thomas
      Hi,

      die Konsolenanwendung habe ich einfach aus dem Grund gewählt, weil der Server ja grundsätzlich kein GUI braucht. Natürlich kannst du den Server auch in eine Windows-Forms-Anwendung packen. Das Grundproblem ist nicht die Endlosschleife (die sich übrigens nur nach jeder neuen Verbindung in der Ausführung wiederholt), sondern die Stelle, wo so lange mit der Ausführung gewartet wird, bis eine neue Verbindung eingeht. Resultat ist eine Form ohne Reaktion, die also "einfriert".

      Die Stelle mit dem Warten auf eine Verbindung kannst du daher einfach in einen separaten Thread packen. Somit sind auch weitere Benutzereingaben möglich. Bei der Konsole war das nicht nötig, bei einer Windows-Forms-Anwendung ist es das durchaus.

      LG

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

      Oder den Client als Konsole lassen und einfach per Messages mit einer GUI Form kommunizieren lassen, die nur gestartet wird wenn sie benötigt wird...
      Und nicht vergessen die Verbindung vom Client auch beenden zu lassen, in dem Code hier ist ja nichts dergleichen vorhanden.
      Also wer da CnP macht fällt schnell auf die Nase :P

      kevin89 schrieb:

      Hi,

      die Konsolenanwendung habe ich einfach aus dem Grund gewählt, weil der Server ja grundsätzlich kein GUI braucht. Natürlich kannst du den Server auch in eine Windows-Forms-Anwendung packen. Das Grundproblem ist nicht die Endlosschleife (die sich übrigens nur nach jeder neuen Verbindung in der Ausführung wiederholt), sondern die Stelle, wo so lange mit der Ausführung gewartet wird, bis eine neue Verbindung eingeht. Resultat ist eine Form ohne Reaktion, die also "einfriert".

      Die Stelle mit dem Warten auf eine Verbindung kannst du daher einfach in einen separaten Thread packen. Somit sind auch weitere Benutzereingaben möglich. Bei der Konsole war das nicht nötig, bei einer Windows-Forms-Anwendung ist es das durchaus.

      LG
      Hallo,

      sorry das es bei mir etwas länger dauerte bis ich jetzt wieder dazu kam hab jetzt endlich meine Diplomprüfung zu ende gebracht. Aber zum eigentlichen Thema: Ich steh gerade etwas auf dem Kopf, möglicherweise wegen dem Threading.. werde mich da wohl mal einlesen müssen! So wie ich dich verstehe würde ich mit dem warten der Verbindung also : While True in einen Thread Packen wie du das jetzt mit ListenToConnection gemacht hast??? Heißt das die Dinge Parallel laufen?? Das wäre ja der Hammer...

      Vielen Dank jetzt schon für den Denkanstoß!
      Thomas
      Hallo Thomas,

      ja - so ungefähr. "Der Hammer" wäre es trotzdem nicht, denn der Server führt wirklich nur Code aus, wenn sich entweder ein Client verbindet, trennt, oder etwas sendet. Insofern ist der Server die meiste Zeit in einem "Schlaf-Zustand" und verbraucht damit praktisch keine Systemresourcen.

      Hier ein Beispiel:

      VB.NET-Quellcode

      1. Private mainthread As New System.Threading.Thread(AddressOf ListenToNewConnection)

      VB.NET-Quellcode

      1. Private Sub ListenToNewConnection()
      2. Console.WriteLine("Der Server läuft!")
      3. server = New TcpListener(ipendpoint)
      4. server.Start()
      5. While True ' wir warten auf eine neue verbindung...
      6. client = server.AcceptTcpClient
      7. Dim c As New Connection ' und erstellen für die neue verbindung eine neue connection...
      8. c.stream = client.GetStream
      9. c.streamr = New StreamReader(c.stream)
      10. c.streamw = New StreamWriter(c.stream)
      11. c.nick = c.streamr.ReadLine ' falls das mit dem nick nicht gewünscht, auch diese zeile entfernen.
      12. list.Add(c) ' und fügen sie der liste der clients hinzu.
      13. Console.WriteLine(c.nick & " has joined.")
      14. ' falls alle anderen das auch lesen sollen können, an alle clients weiterleiten. siehe SendToAllClients
      15. Dim t As New Threading.Thread(AddressOf ListenToConnection)
      16. t.Start(c)
      17. End While
      18. End Sub

      VB.NET-Quellcode

      1. Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
      2. mainthread.Start()
      3. End Sub


      Du siehst: Das einzige was ich zusätzlich geschrieben habe beträgt gerade mal 2 Zeilen und die dürften verständlich sein. Außerdem habe ich den Namen der Main-Prozedur abgeändert, was man wohl nicht als großen Akt bezeichnen könnte. So kompliziert ist das gar nicht. Nicht vergessen den Thread beim Schließen zu stoppen und die Verbindungen zu schließen. Mit diesem Code läuft das Warten auf neue Verbindungen in einem Extra-Thread und die Form lässt sich problemlos bedienen.

      LG

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

      Hallo,

      also das funktioniert soweit alles spitze, die Form friert nicht ein, der Client kann sich sogar Connecten. Allerdings gibts bei den ganzen Dingen ein kleines Problem und zwar ist ja der Zugriff zwischen Threads wie ich gelesen habe nicht so ohne weiteres möglich. Ich hab das jetzt wie folgt gelöst:

      VB.NET-Quellcode

      1. Control.CheckForIllegalCrossThreadCalls = False


      Aber ist das eine gute Möglichkeit oder würde das noch schöner gehn? Das nächste Problem ist ja das ich jetzt irgendwie mitbekommen muss wann sich ein Client "verabschiedet", disconnected etc... Oder soll(muss) man dies hier mit einer regelmäßigen Kontaktaufnahme selber herausfinden ob es den Clienten noch gibt? Als alte vb6 Idee hab ich da erstmal nur ein OnDisconnect Event???? Aber ich denke das geht in die falsche Richtung oder?

      Vielen Dank für den Denkanstoß!

      vg Thomas