前言
看了無聞老師的一節關於 goroutine 與 channel 的講解課堂,感覺不是很明白,所以決定來實現一個聊天室的功能
為什麼是群聊呢?
因為群聊相對邏輯簡單些
註:本栗子只用到了 goroutine 並沒有用到 channel
概述
1.聊天室的組成
聊天室分為兩個部分,分別是:
然後,一般情況下我們互相聊天使用的都只是用戶端而已,服務端只是起到調度的作用
2.資訊發送與接收的流程
假設我們有 服務端(S) 用戶端(C1) 用戶端(C2) 用戶端(C3)
並且 S 已經 與 C1 C2 C3 建立了串連
理論上的流程是這樣的:
- C1 向 S 發出資訊
- S 接收到資訊
- S 將接收到的資訊廣播給 C2 C3
- C2 C3 接收資訊
實踐
1.服務端
1) 代碼
package mainimport ( "time" "fmt" "net")// 用戶端 map var client_map = make(map[string]*net.TCPConn)// 監聽請求func listen_client(ip_port string) { tcpAddr, _ := net.ResolveTCPAddr("tcp", ip_port) tcpListener, _ := net.ListenTCP("tcp", tcpAddr) for {// 不停地接收 client_con, _ := tcpListener.AcceptTCP()// 監聽請求串連 client_map[client_con.RemoteAddr().String()] = client_con// 將串連添加到 map go add_receiver(client_con) fmt.Println("使用者 : ", client_con.RemoteAddr().String(), " 已串連.") }}// 向串連添加接收器func add_receiver(current_connect *net.TCPConn) { for { byte_msg := make([]byte, 2048) len, err := current_connect.Read(byte_msg) if err != nil { current_connect.Close() } fmt.Println(string(byte_msg[:len])) msg_broadcast(byte_msg[:len], current_connect.RemoteAddr().String()) }}// 廣播給所有 clientfunc msg_broadcast(byte_msg []byte, key string) { for k, con := range client_map { if k != key { con.Write(byte_msg) } }}// 主函數func main() { fmt.Println("服務已啟動...") time.Sleep(1 * time.Second) fmt.Println("等待用戶端請求串連...") go listen_client("127.0.0.1:1801") select{}}
b) 描述
可以看到,撇開 main 函數,一共有 2 個 routine,分別是:
- 監聽串連
- 接收訊息(廣播訊息也在這裡)
2.用戶端
a) 代碼
package mainimport ( "fmt" "net" "os" "bufio")// 使用者名稱var login_name string// 本機串連var self_connect *net.TCPConn// 讀取行文本var reader = bufio.NewReader(os.Stdin)// 建立串連func connect(addr string) { tcp_addr, _ := net.ResolveTCPAddr("tcp", addr) // 使用tcp con, err := net.DialTCP("tcp", nil, tcp_addr) // 撥號 self_connect = con if err != nil { fmt.Println("伺服器串連失敗") os.Exit(1) } go msg_sender() go msg_receiver()}// 訊息接收器func msg_receiver() { buff := make([]byte, 2048) for { len, _ := self_connect.Read(buff) // 讀取訊息 fmt.Println(string(buff[:len])) }}// 訊息發送器func msg_sender() { for { read_line_msg, _, _ := reader.ReadLine() read_line_msg = []byte(login_name + " : " + string(read_line_msg)) self_connect.Write(read_line_msg) }}// 主函數func main() { fmt.Println("請問您怎麼稱呼?") name, _, _ := reader.ReadLine() login_name = string(name) connect("127.0.0.1:1801") select{}}
b) 描述
同樣,用戶端也是有兩個 routine 組成:
- 訊息接收器
- 訊息發送器
建立串連在主線程完成
3.
a) 服務端
b) 用戶端_1
c) 用戶端_2
結尾
這裡並沒有用到 channel
小栗子僅為經驗總結,學習交流而記