```go
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 254.
// Chat is a server that lets clients chat with each other.
package main
import (
"bufio"
"fmt"
"log"
"net"
)
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
)
//负责广播的goroutine,抱有疑问,这个函数用一个for死循环嵌套了一个select,通过select去选择性地匹配某一个case,但这些case中好像没有与clientWriter的goroutine通过channel之间进行交流呢,我不太理解,从handleConn中接受的messages是如何通过broadcaster与clientWriter广播写入连接conn中去的呢?
//entering与leaving在整个过程中又起到什么作用呢?如果去掉这两个通道又会如何?
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)
}
}
}
//处理连接的goroutine,每一个客户端与服务器建立的连接都会新开一个goroutine,相互独立。在这个函数中,又开了一个goroutine,并发执行clientWriter,将handleConn的局部变量写入conn中。
func handleConn(conn net.Conn) {
ch := make(chan string) // outgoing client messages
go clientWriter(conn, ch)
who := conn.RemoteAddr().String()
//1.用A客户端建立连接,“You are ‘主机ip’”传入局部通道ch,此时执行clientWriter的goroutine将结束阻塞,将传入通道的“You are ‘主机ip’”写入连接conn,“You are ‘主机ip’”会返回至A客户端终端中显示。
ch <- "You are " + who
//2.“‘主机ip’ has arrived”传入全局通道messages中,“‘主机ip’ has arrived”不会返回至A客户端终端中,如果此时有B客户端与服务器保持连接,那么“‘主机ip’ has arrived”会返回至B客户端,这是为什么?
messages <- who + " has arrived"
//3.ch中的消息传给entering这个通道去,我不是很理解这一句话,ch是一个无缓冲通道,即容量为1,刚才注释第一点说了,clientWriter会将通道ch中的消息接收,这样ch中不就没有值了么,这样enter <- ch不就阻塞了么?
entering <- ch
//4.通过扫描conn中的客户端A写入conn中的信息,将其取出,传入messages通道中,即“‘主机ip’:‘A客户端写入conn的信息’”;最后这个字符串“‘主机ip’:‘A客户端写入conn的信息’”会在A,B客户端的终端显示,这是为什么呢?执行broadcaster()的goroutine并没有将messages的内容发送至某处并将其写入conn的操作啊?
input := bufio.NewScanner(conn)
for input.Scan() {
messages <- who + ": " + input.Text()
}
// NOTE: ignoring potential errors from input.Err()
//5.若客户端A写入连接“control+z”,那么ch会发送至通道leaving,但ch里面不是没有值么,不是会阻塞么?"主机A has left"会发送至通道messages,最后B客户端终端会显示"主机A has left"。
leaving <- ch
messages <- who + " has left"
conn.Close()
}
//负责写入conn的goroutine,将单向in通道ch的消息写入conn中(每条连接是相互独立的goroutine)
func clientWriter(conn net.Conn, ch <-chan string) {
for msg := range ch {
fmt.Fprintln(conn, msg) // NOTE: ignoring network errors
}
}
//三个goroutine(broadcaster,handleConn和clientWriter)通过channel进行通信。
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)
}
}
```
**总结一下我的问题:**
1.(Line 54)“‘主机ip’ has arrived”传入全局通道messages中,“‘主机ip’ has arrived”不会返回至A客户端终端中,如果此时有B客户端与服务器保持连接,那么“‘主机ip’ has arrived”会返回至B客户端,这是为什么?
2.(Line56-59)通过扫描conn中的客户端A写入conn中的信息,将其取出,传入messages通道中,即“‘主机ip’:‘A客户端写入conn的信息’”;最后这个字符串“‘主机ip’:‘A客户端写入conn的信息’”会在A,B客户端的终端显示,这是为什么呢?执行broadcaster()的goroutine并没有将messages的内容发送至某处并将其写入conn的操作啊?
3.(Line23-40,function broadcaster)负责广播的goroutine,抱有疑问,这个函数用一个for死循环嵌套了一个select,通过select去选择性地匹配某一个case,但这些case中好像没有与clientWriter的goroutine通过channel之间进行交流呢,我不太理解,从handleConn中接受的messages是如何通过broadcaster与clientWriter广播写入连接conn中去的呢?
4.(Line55)ch中的消息传给entering这个通道去,我不是很理解这一句话,ch是一个无缓冲通道,即容量为1,而clientWriter会将通道ch中的消息接收,这样ch中不就没有值了么,这样enter <- ch不就阻塞了么?
5.(Line61)若客户端A写入连接“control+z”,那么ch会发送至通道leaving,但ch里面不是没有值么,不是会阻塞么?”主机A has left”会发送至通道messages,最后B客户端终端会显示”主机A has left”。
6.entering与leaving这两个全局通道在整个过程中又起到什么作用呢?如果去掉这两个通道又会如何?
谢谢,希望有哪位能回答我的疑惑~感激不尽!!
有疑问加站长微信联系(非本文作者)