8.10 示例: 聊天服務
代碼
func test_ex_chat() {
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_ex813(conn)
}
}
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
//練習 8.12: 使broadcaster能夠將arrival事件通知當前所有的客戶端。
// 爲了達成這個目的,你需要有一個客戶端的集合,
// 並且在entering和leaving的channel中記錄客戶端的名字。
clientMap = make(map[string]string)
oldKey = int64(10001)
)
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 {
//練習 8.15: 如果一個客戶端沒有及時地讀取數據可能會導致所有的客戶端被阻塞。
// 修改broadcaster來跳過一條消息,而不是等待這個客戶端一直到其準備好寫
if !clients[cli] {
continue
}
cli <- msg
}
case cli := <-entering:
clients[cli] = true
case cli := <-leaving:
delete(clients, cli)
close(cli)
}
}
}
func handleConn_ex(conn net.Conn) {
ch := make(chan string) // outgoing client messages
go clientWriter(conn, ch)
who := conn.RemoteAddr().String()
//練習 8.14:
// 修改聊天服務器的網絡協議這樣每一個客戶端就可以在entering時可以提供它們的名字。
// 將消息前綴由之前的網絡地址改爲這個名字。
if len(clientMap[who]) <= 0 {
str := "num" + fmt.Sprint(oldKey)
clientMap[who] = str
oldKey++
}
ch <- "You are " + clientMap[who]
messages <- clientMap[who] + " has arrived"
entering <- ch
input := bufio.NewScanner(conn)
for input.Scan() {
messages <- clientMap[who] + ": " + input.Text()
}
// NOTE: ignoring potential errors from input.Err()
leaving <- ch
messages <- clientMap[who] + " has left"
conn.Close()
}
//練習 8.13: 使聊天服務器能夠斷開空閒的客戶端連接,
// 比如最近五分鐘之後沒有發送任何消息的那些客戶端。
// 提示:可以在其它goroutine中調用conn.Close()來解除Read調用,
// 就像input.Scanner()所做的那樣
func handleConn_ex813(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)
abort := make(chan string)
go func() {
for {
select {
case <-time.After(5 * time.Minute):
conn.Close()
case str := <-abort:
messages <- str
}
}
}()
for input.Scan() {
str := input.Text()
if str == "exit" {
break
}
if len(str) > 0 {
abort <- 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
}
}
——不足之處,歡迎補充——
備註
《Go 語言聖經》
- 學習記錄所使用的GO版本是1.8
- 學習記錄所使用的編譯器工具爲GoLand
- 學習記錄所使用的系統環境爲Mac os
- 學習者有一定的C語言基礎
代碼倉庫