A few confused in the go programming language chat server

I’m reading the go programming language. In chatper8, I can’t understand how to broadcast mesage to many connected client. Here is the code.

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
)

//!+broadcaster
type client chan<- string // an outgoing message channel

var (
	entering = make(chan client)
	leaving  = make(chan client)
	messages = make(chan string) // all incoming client messages
)

func broadcaster() {
	clients := make(map[client]bool) // all connected clients
	for {
		select {
		case msg := <-messages:
			// Broadcast incoming message to all
			// clients' outgoing message channels.
			for cli := range clients {
				cli <- msg
			}

		case cli := <-entering:
			clients[cli] = true

		case cli := <-leaving:
			delete(clients, cli)
			close(cli)
		}
	}
}

//!-broadcaster

//!+handleConn
func handleConn(conn net.Conn) {
	ch := make(chan string) // outgoing client messages
	go clientWriter(conn, ch)

	who := conn.RemoteAddr().String()
	ch <- "You are " + who
	messages <- who + " has arrived"
	entering <- ch

	input := bufio.NewScanner(conn)
	for input.Scan() {
		messages <- who + ": " + input.Text()
	}
	// NOTE: ignoring potential errors from input.Err()

	leaving <- ch
	messages <- who + " has left"
	conn.Close()
}

func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg) // NOTE: ignoring network errors
	}
}

//!-handleConn

//!+main
func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}

	go broadcaster()
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

This is my confused code.

func broadcaster() {
	clients := make(map[client]bool) // all connected clients
	for {
		select {
		case msg := <-messages:
			// Broadcast incoming message to all
			// clients' outgoing message channels.
			for cli := range clients {
				cli <- msg
			}

		case cli := <-entering:
			clients[cli] = true

		case cli := <-leaving:
			delete(clients, cli)
			close(cli)
		}
	}
}

Sending broadcast message to client’s outgoing chanel, but this is no code to receive this message in connected goroutine(handleConn) and write this message to network. But the client can receive the broadcast message, Why?

./netcat                                     
You are 127.0.0.1:53490                                  ./netcat
                                                         You are 127.0.0.1:53565
127.0.0.1:53565 has arrived
                                                         Hi
                                                         127.0.0.1:53565: Hi
127.0.0.1:53565: Hi

After thinking, I change this code to below.

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
)

//!+broadcaster
type client struct {
	clientConn net.Conn
	name       string
}

var (
	entering = make(chan client)
	leaving  = make(chan client)
	messages = make(chan string) // all incoming client messages
)

var clients = make(map[string]client) // all connect clients

func broadcaster() {
	for {
		select {
		case msg := <-messages:
			// Broadcast incoming message to all
			// clients' conn.
			for _, cli := range clients {
				go clientWriter(cli.clientConn, msg)
			}

		case cli := <-entering:
			clients[cli.name] = cli

		case cli := <-leaving:
			delete(clients, cli.name)
		}
	}
}

//!-broadcaster

//!+handleConn
func handleConn(conn net.Conn) {
	who := conn.RemoteAddr().String()
	go clientWriter(conn, "You are "+who)
	messages <- who + " has arrived"
	entering <- client{conn, who}

	input := bufio.NewScanner(conn)
	for input.Scan() {
		messages <- who + ": " + input.Text()
	}
	// NOTE: ignoring potential errors from input.Err()

	leaving <- client{conn, who}
	messages <- who + " has left"
	conn.Close()
}

func clientWriter(conn net.Conn, msg string) {
	fmt.Fprintln(conn, msg) // NOTE: ignoring network errors (write message to client)
}

//!-handleConn

//!+main
func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}

	go broadcaster()
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Print(err)
			continue
		}
		go handleConn(conn)
	}
}

I define a new struct named client contain net.Conn object and client’s name. Instead of old code, I use global variable clients to represent all connected clients. When message coming, I use a for loop send message to all connected clients. There just one goroutine to maintain clients status, so I don’t need to use Lock to handle Race Condition. Thank you for watching.

1 Like