How to resolve the algorithm Chat server step by step in the Go programming language
How to resolve the algorithm Chat server step by step in the Go programming language
Table of Contents
Problem Statement
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.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Chat server step by step in the Go programming language
Overview
This Go program implements a simple chat server using the TCP protocol. It allows multiple clients to connect and send messages to each other.
Code Structure
- main.go: Main entry point of the program.
- chatserver.go: Defines the
Server
andconn
types and theListenAndServe
function. - chatconn.go: Defines the
conn
type and its methods.
Detailed Explanation
Server
Type
type Server struct {
add chan *conn // To add a connection
rem chan string // To remove a connection by name
msg chan string // To send a message to all connections
stop chan bool // To stop early
}
The Server
type manages the connections and communication between chat clients. It has four channels:
add
: To add a new connection.rem
: To remove a connection by its name.msg
: To broadcast messages to all connections.stop
: To signal that the server should stop running.
ListenAndServe
Function
func ListenAndServe(addr string) error {
// ...
}
The ListenAndServe
function listens for incoming TCP connections on the specified address (addr
). It creates a new Server
and delegates the handling of connections to the handleConns
goroutine.
handleConns
Method
func (s *Server) handleConns() {
// ...
}
The handleConns
method is a goroutine that manages the addition, removal, and messaging of connections. It maintains a conns
map to track all active connections.
When a new connection is added (case c := <-s.add
), the server checks if the name is already taken. If not, it adds the connection to the conns
map and broadcasts a message to all clients announcing the new connection.
When a message is received (case str := <-s.msg
), the server broadcasts it to all clients.
When a connection is removed (case name := <-s.rem
), the server closes the connection and removes it from the conns
map. It also broadcasts a message announcing the disconnection.
When the server is stopped (case <-s.stop
), it closes all connections and broadcasts a stop message.
conn
Type
type conn struct {
*bufio.Reader // buffered input
net.Conn // raw connection
server *Server // the Server on which the connection arrived
name string
}
The conn
type represents a single client connection. It embeds bufio.Reader
and net.Conn
so that it implements the io.ReadWriteCloser
interface.
newConn
Function
func newConn(s *Server, rwc net.Conn) *conn {
// ...
}
The newConn
function creates a new conn
using the provided server and network connection.
welcome
Method
func (c *conn) welcome() {
// ...
}
The welcome
method asks the client to enter their name. It loops until the client provides a non-empty name. If the name is already taken, it informs the client and prompts them to enter a new name.
readloop
Method
func (c *conn) readloop() {
// ...
}
The readloop
method is a goroutine that reads messages from the client and broadcasts them to all other clients. It continues reading until the client disconnects. When the client disconnects, it informs the server to remove the connection.
Source code in the go programming language
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))
}
// A Server represents a chat server that accepts incoming connections.
type Server struct {
add chan *conn // To add a connection
rem chan string // To remove a connection by name
msg chan string // To send a message to all connections
stop chan bool // To stop early
}
// ListenAndServe listens on the TCP network address addr for
// new chat client connections.
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 {
// TODO use AcceptTCP() so that we can get a TCPConn on which
// we can call SetKeepAlive() and SetKeepAlivePeriod()
rwc, err := ln.Accept()
if err != nil {
// TODO Could handle err.(net.Error).Temporary()
// here by adding a backoff delay.
close(s.stop)
return err
}
log.Println("New connection from", rwc.RemoteAddr())
go newConn(s, rwc).welcome()
}
}
// handleConns is run as a go routine to handle adding and removal of
// chat client connections as well as broadcasting messages to them.
func (s *Server) handleConns() {
// We define the `conns` map here rather than within Server,
// and we use local function literals rather than methods to be
// extra sure that the only place that touches this map is this
// method. In this way we forgo any explicit locking needed as
// we're the only go routine that can see or modify this.
conns := make(map[string]*conn)
var dropConn func(string)
writeAll := func(str string) {
log.Printf("Broadcast: %q", str)
// TODO handle blocked connections
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 all the disconnect messages until after
// we've closed all currently problematic conns.
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
}
}
}
// A conn represents the server side of a single chat connection.
// Note we embed the bufio.Reader and net.Conn (and specifically in
// that order) so that a conn gets the appropriate methods from each
// to be a full io.ReadWriteCloser.
type conn struct {
*bufio.Reader // buffered input
net.Conn // raw connection
server *Server // the Server on which the connection arrived
name string
}
func newConn(s *Server, rwc net.Conn) *conn {
return &conn{
Reader: bufio.NewReader(rwc),
Conn: rwc,
server: s,
}
}
// welcome requests a name from the client before attempting to add the
// named connect to the set handled by the server.
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)
}
// The server will take this *conn and do a final check
// on the name, possibly starting c.welcome() again.
c.server.add <- c
}
// readloop is started as a go routine by the server once the initial
// welcome phase has completed successfully. It reads single lines from
// the client and passes them to the server for broadcast to all chat
// clients (including us).
// Once done, we ask the server to remove (and close) our connection.
func (c *conn) readloop() {
for {
msg, err := c.ReadString('\n')
if err != nil {
break
}
//msg = strings.TrimSpace(msg)
c.server.msg <- c.name + "> " + msg
}
c.server.rem <- c.name
}
You may also check:How to resolve the algorithm Singly-linked list/Element insertion step by step in the ALGOL 68 programming language
You may also check:How to resolve the algorithm Execute a system command step by step in the Bracmat programming language
You may also check:How to resolve the algorithm Jordan-Pólya numbers step by step in the C programming language
You may also check:How to resolve the algorithm ISBN13 check digit step by step in the C# programming language
You may also check:How to resolve the algorithm Peripheral drift illusion step by step in the Delphi programming language