go语言圣经,关于第八章并发最后一节的chat.go有些点不太懂,求教

a0s1d3f4 · · 997 次点击
上一个回复没写完。 理解几个变量的意义: ch 通道: 可以理解为与客户端连接绑定的通道,在clientWriter中从ch中读取数据写入客户端连接conn。 clients Map: 存放与客户端连接绑定的通道,即当前连接的客户端列表。 messages 通道: 广播消息,写入数据会广播到所有客户端(broadcaster中处理)。 entering 通道:客户端上线通道,取到的数据就是与客户端连接绑定的通道,放入clients。 leaving 通道:客户端离线通道,取到的数据就是与客户端连接绑定的通道,从clients中移除。 1.(Line 54)“‘主机ip’ has arrived”传入全局通道messages中,“‘主机ip’ has arrived”不会返回至A客户端终端中,如果此时有B客户端与服务器保持连接,那么“‘主机ip’ has arrived”会返回至B客户端,这是为什么? >此时写入messages中,会被广播到所有已上线(指放入clients)的客户端,还没有把与客户端连接绑定的通道ch放入clients中,所以客户端A不会受到消息。 2.(Line56-59)通过扫描conn中的客户端A写入conn中的信息,将其取出,传入messages通道中,即“‘主机ip’:‘A客户端写入conn的信息’”;最后这个字符串“‘主机ip’:‘A客户端写入conn的信息’”会在A,B客户端的终端显示,这是为什么呢?执行broadcaster()的goroutine并没有将messages的内容发送至某处并将其写入conn的操作啊? >在broadcaster中,从messages取出数据,会循环写入clients中所有的通道中,也就是所有与通道绑定的通道ch,而在clientWrite方法中会从通道ch中取出数据,写入客户端端连接conn中。 3.(Line23-40,function broadcaster)负责广播的goroutine,抱有疑问,这个函数用一个for死循环嵌套了一个select,通过select去选择性地匹配某一个case,但这些case中好像没有与clientWriter的goroutine通过channel之间进行交流呢,我不太理解,从handleConn中接受的messages是如何通过broadcaster与clientWriter广播写入连接conn中去的呢? >clients中存放的就是与客户端绑定的通道ch,也就是clientWriter实现的从ch读数据写入conn,通过往ch中写入数据,间接写入客户端连接conn。 4.(Line55)ch中的消息传给entering这个通道去,我不是很理解这一句话,ch是一个无缓冲通道,即容量为1,而clientWriter会将通道ch中的消息接收,这样ch中不就没有值了么,这样enter <- ch不就阻塞了么? >entering <- ch这句是将通道ch写入entering ,entering 和leaving通道接受的类型是client类型,也就是通道。 5.(Line61)若客户端A写入连接“control+z”,那么ch会发送至通道leaving,但ch里面不是没有值么,不是会阻塞么?”主机A has left”会发送至通道messages,最后B客户端终端会显示”主机A has left”。 >entering <- ch这句是将通道ch写入entering ,entering 和leaving通道接受的类型是client类型,也就是通道。 6.entering与leaving这两个全局通道在整个过程中又起到什么作用呢?如果去掉这两个通道又会如何? 谢谢,希望有哪位能回答我的疑惑~感激不尽!! >entering与leaving这两个通道实现了客户端的上线与离线,去掉后,将不能在客户端上线或离线时及时感知并通知所有在线客户端。
#4
更多评论
没看过这本书,这段代码看了几遍,写的真心奇葩。 问题1: 代码中50行开始群发消息,52行才把A客户端加入队列 (52行加入entering队列,34行从entering队列加入clients,32行-32行负责对所有clients里的内容进行广播) 所以这是先发送消息再注册客户端的问题。 问题2: 发送消息在67-69行,不清楚range一个chan的用法的话参考 https://tour.golang.org/concurrency/4 问题3: 同2。你可以理解每一个entering是一个消息队列。通过这个来发送的 问题4. entering有消费者啊,第33行。 问题5 同问题4,第35行 问题6 起到不同协程之间通信的作用,分配任务和数据。 去掉你就没法调控各个线程了。
#1
简单回答一下: 1. ``` messages <- who + " has arrived" ``` 这句是向 messages 传递了一个字符串,而 messages 拿到后会向所有的已经进入 clients 这个 map 的客户端传递,此时,你所谓的 A客户端 还并未被进入 clients,这个进入动作要到 entering <- ch 才会被执行 2. 同上,此时执行的 ``` messages <- who + ": " + input.Text() ``` 已经是当前的 A客户端 进入 clients 之后了,自然会收到消息了。 3. 需要注意到 ``` var ( entering = make(chan client) leaving = make(chan client) messages = make(chan string) // all incoming client messages ) ``` 这是三个全局变量,保证了 broadcaster 与 handleConn 的交流,而 44 行的 ch := make(chan string) 则保证了 handleConn 与 clientWriter 的交流 4. ch是一个无缓冲通道,即容量为1 这句话错了,无缓冲渠道,容量为 0. entering 是个以 chan<- string 为传递值得渠道,也就是说 entering 里面存储的是渠道,而不是 string。 33 行 case cli := <-entering: 保证了不会阻塞在 52 行。 enter <- ch 做的事情是把这个 ch 作为一个值传递给 enter 这个渠道,而不是取里面的值。 for msg := range ch 才是真正的取 ch 这个渠道的值。 5. 跟4 一样,ch 是作为一个值被传递给 leaving, 而不是取里面的值。 6. entering leaving 只是作为 tcp 链接的一个中间状态而已,去掉也可以,这是较为熟悉渠道的人做的补充,而对于不太熟悉渠道的就觉得迷惑了。
#2