mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-05 15:12:33 +00:00
zz
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
## 8.10. 示例: 聊天服務
|
||||
|
||||
我们用一个聊天服务器来终结本章节的内容,这个程序可以让一些用户通过服务器向其它所有用户广播文本消息。这个程序中有四种goroutine。main和broadcaster各自是一个goroutine实例,每一个客户端的连接都会有一个handleConn和clientWriter的goroutine。broadcaster是select用法的不错的样例,因为它需要处理三种不同类型的消息。
|
||||
下面演示的main goroutine的工作,是listen和accept(译注:网络编程里的概念)从客户端过来的连接。对每一个连接,程序都会建立一个新的handleConn的goroutine,就像我们在本章开头的并发的echo服务器里所做的那样。
|
||||
我們用一個聊天服務器來終結本章節的內容,這個程序可以讓一些用戶通過服務器向其它所有用戶廣播文本消息。這個程序中有四種goroutine。main和broadcaster各自是一個goroutine實例,每一個客戶端的連接都會有一個handleConn和clientWriter的goroutine。broadcaster是select用法的不錯的樣例,因爲它需要處理三種不同類型的消息。
|
||||
下面演示的main goroutine的工作,是listen和accept(譯註:網絡編程里的概念)從客戶端過來的連接。對每一個連接,程序都會建立一個新的handleConn的goroutine,就像我們在本章開頭的併發的echo服務器里所做的那樣。
|
||||
|
||||
```go
|
||||
gopl.io/ch8/chat
|
||||
@@ -22,7 +22,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
然后是broadcaster的goroutine。他的内部变量clients会记录当前建立连接的客户端集合。其记录的内容是每一个客户端的消息发出channel的"资格"信息。
|
||||
然後是broadcaster的goroutine。他的內部變量clients會記録當前建立連接的客戶端集合。其記録的內容是每一個客戶端的消息發齣channel的"資格"信息。
|
||||
|
||||
|
||||
```go
|
||||
@@ -55,9 +55,9 @@ func broadcaster() {
|
||||
}
|
||||
```
|
||||
|
||||
broadcaster监听来自全局的entering和leaving的channel来获知客户端的到来和离开事件。当其接收到其中的一个事件时,会更新clients集合,当该事件是离开行为时,它会关闭客户端的消息发出channel。broadcaster也会监听全局的消息channel,所有的客户端都会向这个channel中发送消息。当broadcaster接收到什么消息时,就会将其广播至所有连接到服务端的客户端。
|
||||
broadcaster監聽來自全局的entering和leaving的channel來穫知客戶端的到來和離開事件。當其接收到其中的一個事件時,會更新clients集合,當該事件是離開行爲時,它會關閉客戶端的消息發齣channel。broadcaster也會監聽全局的消息channel,所有的客戶端都會向這個channel中發送消息。當broadcaster接收到什麽消息時,就會將其廣播至所有連接到服務端的客戶端。
|
||||
|
||||
现在让我们看看每一个客户端的goroutine。handleConn函数会为它的客户端创建一个消息发出channel并通过entering channel来通知客户端的到来。然后它会读取客户端发来的每一行文本,并通过全局的消息channel来将这些文本发送出去,并为每条消息带上发送者的前缀来标明消息身份。当客户端发送完毕后,handleConn会通过leaving这个channel来通知客户端的离开并关闭连接。
|
||||
現在讓我們看看每一個客戶端的goroutine。handleConn函數會爲它的客戶端創建一個消息發齣channel併通過entering channel來通知客戶端的到來。然後它會讀取客戶端發來的每一行文本,併通過全局的消息channel來將這些文本發送齣去,併爲每條消息帶上發送者的前綴來標明消息身份。當客戶端發送完畢後,handleConn會通過leaving這個channel來通知客戶端的離開併關閉連接。
|
||||
|
||||
```go
|
||||
func handleConn(conn net.Conn) {
|
||||
@@ -87,9 +87,9 @@ func clientWriter(conn net.Conn, ch <-chan string) {
|
||||
}
|
||||
```
|
||||
|
||||
另外,handleConn为每一个客户端创建了一个clientWriter的goroutine来接收向客户端发出消息channel中发送的广播消息,并将它们写入到客户端的网络连接。客户端的读取方循环会在broadcaster接收到leaving通知并关闭了channel后终止。
|
||||
另外,handleConn爲每一個客戶端創建了一個clientWriter的goroutine來接收向客戶端發齣消息channel中發送的廣播消息,併將它們寫入到客戶端的網絡連接。客戶端的讀取方循環會在broadcaster接收到leaving通知併關閉了channel後終止。
|
||||
|
||||
下面演示的是当服务器有两个活动的客户端连接,并且在两个窗口中运行的情况,使用netcat来聊天:
|
||||
下面演示的是當服務器有兩個活動的客戶端連接,併且在兩個窗口中運行的情況,使用netcat來聊天:
|
||||
```
|
||||
$ go build gopl.io/ch8/chat
|
||||
$ go build gopl.io/ch8/netcat3
|
||||
@@ -113,9 +113,9 @@ You are 127.0.0.1:64216 127.0.0.1:64216 has arrived
|
||||
|
||||
```
|
||||
|
||||
当与n个客户端保持聊天session时,这个程序会有2n+2个并发的goroutine,然而这个程序却并不需要显式的锁(§9.2)。clients这个map被限制在了一个独立的goroutine中,broadcaster,所以它不能被并发地访问。多个goroutine共享的变量只有这些channel和net.Conn的实例,两个东西都是并发安全的。我们会在下一章中更多地解决约束,并发安全以及goroutine中共享变量的含义。
|
||||
當與n個客戶端保持聊天session時,這個程序會有2n+2個併發的goroutine,然而這個程序卻併不需要顯式的鎖(§9.2)。clients這個map被限製在了一個獨立的goroutine中,broadcaster,所以它不能被併發地訪問。多個goroutine共享的變量隻有這些channel和net.Conn的實例,兩個東西都是併發安全的。我們會在下一章中更多地解決約束,併發安全以及goroutine中共享變量的含義。
|
||||
|
||||
练习8.12: 使broadcaster能够将arrival事件通知当前所有的客户端。为了达成这个目的,你需要有一个客户端的集合,并且在entering和leaving的channel中记录客户端的名字。
|
||||
练习8.13: 使聊天服务器能够断开空闲的客户端连接,比如最近五分钟之后没有发送任何消息的那些客户端。提示:可以在其它goroutine中调用conn.Close()来解除Read调用,就像input.Scanner()所做的那样。
|
||||
练习8.14: 修改聊天服务器的网络协议这样每一个客户端就可以在entering时可以提供它们的名字。将消息前缀由之前的网络地址改为这个名字。
|
||||
练习8.15: 如果一个客户端没有及时地读取数据可能会导致所有的客户端被阻塞。修改broadcaster来跳过一条消息,而不是等待这个客户端一直到其准备好写。或者为每一个客户端的消息发出channel建立缓冲区,这样大部分的消息便不会被丢掉;broadcaster应该用一个非阻塞的send向这个channel中发消息。
|
||||
練習8.12: 使broadcaster能夠將arrival事件通知當前所有的客戶端。爲了達成這個目的,你需要有一個客戶端的集合,併且在entering和leaving的channel中記録客戶端的名字。
|
||||
練習8.13: 使聊天服務器能夠斷開空閒的客戶端連接,比如最近五分鐘之後沒有發送任何消息的那些客戶端。提示:可以在其它goroutine中調用conn.Close()來解除Read調用,就像input.Scanner()所做的那樣。
|
||||
練習8.14: 脩改聊天服務器的網絡協議這樣每一個客戶端就可以在entering時可以提供它們的名字。將消息前綴由之前的網絡地址改爲這個名字。
|
||||
練習8.15: 如果一個客戶端沒有及時地讀取數據可能會導致所有的客戶端被阻塞。脩改broadcaster來跳過一條消息,而不是等待這個客戶端一直到其準備好寫。或者爲每一個客戶端的消息發齣channel建立緩衝區,這樣大部分的消息便不會被丟掉;broadcaster應該用一個非阻塞的send向這個channel中發消息。
|
||||
|
Reference in New Issue
Block a user