Task

Write a server for a minimal text based chat.

People should be able to connect via ‘telnet’, sign on with a nickname, and type messages which will then be seen by all other connected users. Arrivals and departures of chat members should generate appropriate notification messages.




Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
 
Module Module1
 
    Class State
        Private ReadOnly client As TcpClient
        Private ReadOnly sb As New StringBuilder
 
        Public Sub New(name As String, client As TcpClient)
            Me.Name = name
            Me.client = client
        End Sub
 
        Public ReadOnly Property Name As String
 
        Public Sub Send(text As String)
            Dim bytes = Encoding.ASCII.GetBytes(String.Format("{0}" & vbCrLf, text))
            client.GetStream().Write(bytes, 0, bytes.Length)
        End Sub
    End Class
 
    ReadOnly connections As New Dictionary(Of Integer, State)
    Dim listen As TcpListener
    Dim serverThread As Thread
 
    Sub Main()
        listen = New TcpListener(Net.IPAddress.Parse("127.0.0.1"), 4004)
        serverThread = New Thread(New ThreadStart(AddressOf DoListen))
        serverThread.Start()
    End Sub
 
    Private Sub DoListen()
        listen.Start()
        Console.WriteLine("Server: Started server")
 
        Do
            Console.Write("Server: Waiting...")
            Dim client = listen.AcceptTcpClient()
            Console.WriteLine(" Connected")
 
            ' New thread with client
            Dim clientThread As New Thread(New ParameterizedThreadStart(AddressOf DoClient))
 
            clientThread.Start(client)
        Loop
    End Sub
 
    Private Sub DoClient(client As TcpClient)
        Console.WriteLine("Client (Thread: {0}): Connected!", Thread.CurrentThread.ManagedThreadId)
        Dim bytes = Encoding.ASCII.GetBytes("Enter name: ")
        client.GetStream().Write(bytes, 0, bytes.Length)
 
        Dim done As Boolean
        Dim name As String
        Do
            If Not client.Connected Then
                Console.WriteLine("Client (Thread: {0}): Terminated!", Thread.CurrentThread.ManagedThreadId)
                client.Close()
                Thread.CurrentThread.Abort() ' Kill thread
            End If
 
            name = Receive(client)
            done = True
 
            For Each cl In connections
                Dim state = cl.Value
                If state.Name = name Then
                    bytes = Encoding.ASCII.GetBytes("Name already registered. Please enter your name: ")
                    client.GetStream().Write(bytes, 0, bytes.Length)
                    done = False
                End If
            Next
        Loop While Not done
 
        connections.Add(Thread.CurrentThread.ManagedThreadId, New State(name, client))
        Console.WriteLine(vbTab & "Total connections: {0}", connections.Count)
        Broadcast(String.Format("+++ {0} arrived +++", name))
 
        Do
            Dim text = Receive(client)
            If text = "/quit" Then
                Broadcast(String.Format("Connection from {0} closed.", name))
                connections.Remove(Thread.CurrentThread.ManagedThreadId)
                Console.WriteLine(vbTab & "Total connections: {0}", connections.Count)
                Exit Do
            End If
 
            If Not client.Connected Then
                Exit Do
            End If
 
            Broadcast(String.Format("{0}> {1}", name, text))
        Loop
 
        Console.WriteLine("Client (Thread: {0}): Terminated!", Thread.CurrentThread.ManagedThreadId)
        client.Close()
        Thread.CurrentThread.Abort()
    End Sub
 
    Private Function Receive(client As TcpClient) As String
        Dim sb As New StringBuilder
        Do
            If client.Available > 0 Then
                While client.Available > 0
                    Dim ch = Chr(client.GetStream.ReadByte())
                    If ch = vbCr Then
                        ' ignore
                        Continue While
                    End If
                    If ch = vbLf Then
                        Return sb.ToString()
                    End If
                    sb.Append(ch)
                End While
 
                ' pause
                Thread.Sleep(100)
            End If
        Loop
    End Function
 
    Private Sub Broadcast(text As String)
        Console.WriteLine(text)
        For Each client In connections
            If client.Key <> Thread.CurrentThread.ManagedThreadId Then
                Dim state = client.Value
                state.Send(text)
            End If
        Next
    End Sub
 
End Module