Task: Chat server




Go: package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"net"
	"strings"
	"time"
)

func main() {
	log.SetPrefix("chat: ")
	addr := flag.String("addr", "localhost:4000", "listen address")
	flag.Parse()
	log.Fatal(ListenAndServe(*addr))
}


type Server struct {
	add  chan *conn  
	rem  chan string 
	msg  chan string 
	stop chan bool   
}



func ListenAndServe(addr string) error {
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	log.Println("Listening for connections on", addr)
	defer ln.Close()
	s := &Server{
		add:  make(chan *conn),
		rem:  make(chan string),
		msg:  make(chan string),
		stop: make(chan bool),
	}
	go s.handleConns()
	for {
		
		
		rwc, err := ln.Accept()
		if err != nil {
			
			
			close(s.stop)
			return err
		}
		log.Println("New connection from", rwc.RemoteAddr())
		go newConn(s, rwc).welcome()
	}
}



func (s *Server) handleConns() {
	
	
	
	
	
	conns := make(map[string]*conn)

	var dropConn func(string)
	writeAll := func(str string) {
		log.Printf("Broadcast: %q", str)
		
		for name, c := range conns {
			c.SetWriteDeadline(time.Now().Add(500 * time.Millisecond))
			_, err := c.Write([]byte(str))
			if err != nil {
				log.Printf("Error writing to %q: %v", name, err)
				c.Close()
				delete(conns, name)
				
				
				defer dropConn(name)
			}
		}
	}

	dropConn = func(name string) {
		if c, ok := conns[name]; ok {
			log.Printf("Closing connection with %q from %v",
				name, c.RemoteAddr())
			c.Close()
			delete(conns, name)
		} else {
			log.Printf("Dropped connection with %q", name)
		}
		str := fmt.Sprintf("--- %q disconnected ---\n", name)
		writeAll(str)
	}

	defer func() {
		writeAll("Server stopping!\n")
		for _, c := range conns {
			c.Close()
		}
	}()

	for {
		select {
		case c := <-s.add:
			if _, exists := conns[c.name]; exists {
				fmt.Fprintf(c, "Name %q is not available\n", c.name)
				go c.welcome()
				continue
			}
			str := fmt.Sprintf("+++ %q connected +++\n", c.name)
			writeAll(str)
			conns[c.name] = c
			go c.readloop()
		case str := <-s.msg:
			writeAll(str)
		case name := <-s.rem:
			dropConn(name)
		case <-s.stop:
			return
		}
	}
}





type conn struct {
	*bufio.Reader         
	net.Conn              
	server        *Server 
	name          string
}

func newConn(s *Server, rwc net.Conn) *conn {
	return &conn{
		Reader: bufio.NewReader(rwc),
		Conn:   rwc,
		server: s,
	}
}



func (c *conn) welcome() {
	var err error
	for c.name = ""; c.name == ""; {
		fmt.Fprint(c, "Enter your name: ")
		c.name, err = c.ReadString('\n')
		if err != nil {
			log.Printf("Reading name from %v: %v", c.RemoteAddr(), err)
			c.Close()
			return
		}
		c.name = strings.TrimSpace(c.name)
	}
	
	
	c.server.add <- c
}






func (c *conn) readloop() {
	for {
		msg, err := c.ReadString('\n')
		if err != nil {
			break
		}
		
		c.server.msg <- c.name + "> " + msg
	}
	c.server.rem <- c.name
}





VB: 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")

            
            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() 
            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
                        
                        Continue While
                    End If
                    If ch = vbLf Then
                        Return sb.ToString()
                    End If
                    sb.Append(ch)
                End While

                
                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