Golang Chat Server
Simple proof of concept chat server
I found it relatively easy to create a simple terminal chat server. To interact,
I am using telnet to connect to the server.
The idea is very simple. In our main thread, we are listening to incoming TCP
connections. So, when telnet connects to our server, we create a separate
goroutine handleConnection to handle that connection.
In handleConnection, we first ask the user for their name, and then create a
client object and register it with the server. After that, we enter a loop where
we continuously read messages from the client, and once the message is received,
we broadcast it to all other connected clients.
The server maintains a list of connected clients and uses channels to handle the
registration, unregistration, and broadcasting of messages. The run method of
the server listens for these events and processes them accordingly.
Full go code:
package main
import (
"bufio"
"fmt"
"net"
"strings"
)
type Client struct {
conn net.Conn
name string
}
type Message struct {
sender net.Conn
text string
}
type Server struct {
clients map[net.Conn]Client
register chan Client
unregister chan Client
broadcast chan Message
}
func (s *Server) run() {
for {
select {
case message := <-s.broadcast:
for conn, client := range s.clients {
if conn != message.sender {
fmt.Fprintln(client.conn, message.text)
}
}
fmt.Println(message.text)
case client := <-s.register:
s.clients[client.conn] = client
text := fmt.Sprintf("%s joined the chat", client.name)
s.broadcast <- Message{sender: client.conn, text: text}
fmt.Printf("%s connected\n", client.name)
case client := <-s.unregister:
if client, ok := s.clients[client.conn]; ok {
delete(s.clients, client.conn)
text := fmt.Sprintf("%s left the chat", client.name)
s.broadcast <- Message{sender: client.conn, text: text}
fmt.Printf("%s disconnected\n", client.name)
}
}
}
}
func (s *Server) handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
fmt.Fprint(conn, "Enter your name: ")
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
client := Client{conn: conn, name: name}
s.register <- client
for {
message, err := reader.ReadString('\n')
if err != nil {
break
}
message = strings.TrimSpace(message)
if message == "" {
continue
}
fullMsg := fmt.Sprintf("%s: %s", name, message)
s.broadcast <- Message{sender: conn, text: fullMsg}
}
s.unregister <- client
}
func NewServer() *Server {
return &Server{
clients: make(map[net.Conn]Client),
register: make(chan Client),
unregister: make(chan Client),
broadcast: make(chan Message, 1),
}
}
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
server := NewServer()
go server.run()
fmt.Println("Chat server started on :8080")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("connection error:", err)
continue
}
go server.handleConnection(conn)
}
}