Trong bài hôm nay chúng ta sẽ thử xây dựng ứng dụng chat bằng chỉ đơn thuần bằng golang và thư viện websockets, không sử dụng bất kỳ ngôn ngữ HTML, CSS, Javascript hay giao diện màu mè, phức tạp nào khác.
Source code: https://github.com/japangermany1998/websockets-go
Giới thiệu
Ta sẽ xây dựng ứng dụng gồm 1 server và 4 client. Client1 và client2 sẽ giao tiếp với nhau trong 1 room, client3 và client4 sẽ giao tiếp với nhau trong 1 room. Mỗi khi 1 client gửi 1 message, server có nhiệm vụ tiếp nhận và phát thanh nó đến toàn bộ các client khác trong cùng 1 room.
Xây dựng server websocket
Như bất kỳ ứng dụng websocket nào, cần có server chứa router để các client có thể kết nối đến.
func runServer() { app := iris.New() //Khai báo instance websocket, server và các client sẽ kết nối đến namespace v1, và cùng bắt sự kiện "chat" var ws = neffos.New(websocket.DefaultGorillaUpgrader, neffos.Namespaces{ "v1": neffos.Events{ "chat": serverReceived, }, }) app.Get("/websocket_endpoint", websocket.Handler(ws)) log.Println("Serving websockets on http://localhost:8080/websocket_endpoint") log.Fatal(app.Listen(":8080"))
}
//Khi server tiếp nhận message tại event "chat" sẽ truyền phát đến các client thuộc cùng 1 namespace, 1 room
func serverReceived(c *neffos.NSConn, msg neffos.Message) error { c.Conn.Server().Broadcast(nil, neffos.Message{ Namespace: msg.Namespace, Room: msg.Room, Event: msg.Event, Body: msg.Body, }) return nil
}
Trong giới hạn của bài này ta chỉ xác định duy nhất 1 namespace "v1" để toàn bộ client và server cùng kết nối vào nó và cùng bắt duy nhất 1 event "chat". Để hiểu sâu hơn về namespace bạn có thể tham khảo ở đây
Kết quả chạy server:
Khởi tạo các client
//khởi chạy client, xác định room để join, tự đặt tên và có thể chọn màu để highlight trên console
func runClient(room, full_name, colors string) { ctx := context.Background() client, err := neffos.Dial(ctx, gorilla.DefaultDialer, "ws://localhost:8080/websocket_endpoint", neffos.Namespaces{ "v1": neffos.Events{ "chat": clientReceive, }, }) if err != nil { panic(err) } //Khởi tạo instance client kết nối đến namespace cụ thể, ở đây là v1 c, err := client.Connect(ctx, "v1") if err != nil { panic(err) } //Khởi tạo instance client join room r, err := c.JoinRoom(ctx, room) if err != nil { panic(err) } fmt.Println("Hello, I'm " + color.Colorize(colors, full_name) + "!") out: for { select { //Trường hợp mất kết nối websocket case <-client.NotifyClose: log.Println(color.Colorize(color.Red, "Websocket connection closed")) break out //Trường hợp kết nối socket thành công default: //Nhập message fmt.Print("Enter message: ") scanner := bufio.NewScanner(os.Stdin) scanner.Scan() // use `for scanner.Scan()` to keep reading msg := scanner.Text() cursor.ClearLinesUp(1) //Client bắn message và gửi đến cho server tại room và namespace xác định và tại event "chat" r.Emit("chat", []byte(color.Colorize(colors, full_name + ": ") + msg)) break } } }
//Client thực hiện lệnh khi nhận được tín hiệu từ server tại event "chat"
func clientReceive(c *neffos.NSConn, msg neffos.Message) error { fmt.Print("\r") log.Println(string(msg.Body)) fmt.Print("Enter message: ") return nil
}
//Khởi tạo server và client switch side { case "server": runServer() //client1 và client2 tại room1 case "client1": runClient("room1", "client1", color.Yellow) case "client2": runClient("room1", "client2", color.Blue) //client3 và client4 tại room2 case "client3": runClient("room2", "client3", color.Green) case "client4": runClient("room2", "client4", color.Cyan) }
Kết quả
Như bạn có thể thấy, client1 và client2 thuộc cùng 1 room nên tiếp nhận được message của nhau. Tương tự với client3 và client4