Golang聊天服务器实现中的几个常见疑问

Golang聊天服务器实现中的几个常见疑问 我正在阅读《Go编程语言》一书。在第8章中,我无法理解如何向多个已连接的客户端广播消息。以下是相关代码。

package main

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

//!+broadcaster
type client chan<- string // 一个出站消息通道

var (
	entering = make(chan client)
	leaving  = make(chan client)
	messages = make(chan string) // 所有客户端的入站消息
)

func broadcaster() {
	clients := make(map[client]bool) // 所有已连接的客户端
	for {
		select {
		case msg := <-messages:
			// 将入站消息广播给所有客户端的出站消息通道。
			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) // 客户端的出站消息
	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()
	}
	// 注意:忽略 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) // 注意:忽略网络错误
	}
}

//!-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)
	}
}

这是让我困惑的代码。

func broadcaster() {
	clients := make(map[client]bool) // 所有已连接的客户端
	for {
		select {
		case msg := <-messages:
			// 将入站消息广播给所有客户端的出站消息通道。
			for cli := range clients {
				cli <- msg
			}

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

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

代码将广播消息发送到客户端的出站通道 ch,但在已连接的 goroutine(handleConn)中,并没有接收此消息并将其写入网络的代码。然而客户端却能收到广播消息,这是为什么?

./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

更多关于Golang聊天服务器实现中的几个常见疑问的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang聊天服务器实现中的几个常见疑问的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


handleConn 函数中,通过 go clientWriter(conn, ch) 启动了一个独立的 goroutine 来专门处理消息发送。clientWriter 函数会持续从通道 ch 中读取消息,并通过 fmt.Fprintln(conn, msg) 写入网络连接。

广播器 broadcaster 将消息发送到每个客户端的 ch 通道,这些消息会被对应的 clientWriter goroutine 接收并发送给客户端。这就是客户端能收到广播消息的原因。

以下是关键代码的说明:

func handleConn(conn net.Conn) {
    ch := make(chan string) // 创建客户端的出站消息通道
    go clientWriter(conn, ch) // 启动独立的goroutine处理消息发送
    
    // ... 其他代码
}

func clientWriter(conn net.Conn, ch <-chan string) {
    for msg := range ch { // 持续从通道读取消息
        fmt.Fprintln(conn, msg) // 将消息写入网络连接
    }
}

func broadcaster() {
    clients := make(map[client]bool)
    for {
        select {
        case msg := <-messages:
            for cli := range clients {
                cli <- msg // 将消息发送到每个客户端的通道
            }
        // ... 其他case
        }
    }
}

广播流程:

  1. 当有消息进入 messages 通道时,broadcaster 遍历所有客户端通道
  2. 将消息发送到每个客户端的 ch 通道
  3. 每个客户端的 clientWriter goroutine 从自己的 ch 通道接收消息
  4. clientWriter 将消息写入对应的网络连接

这种设计模式是典型的 Go 并发模式:每个客户端连接有两个 goroutine 协同工作:

  • handleConn:处理输入(读取客户端消息)
  • clientWriter:处理输出(向客户端发送消息)

两个 goroutine 通过通道 ch 进行通信,实现了并发安全的消息传递。

回到顶部