我们可以看到 gorilla/websocket中的examples中有一个聊天室的demo。
└── src
├── github.com
│ └── gorilla
│ └── websocket
│ ├── examples
│ │ ├── chat
我们进入该项目可以看到里面有这样的一些内容
$ ll
总用量 24K
-rw-r--r-- 1 wangkan wangkan 3.4K 3月 9 12:31 client.go
-rw-r--r-- 1 wangkan wangkan 2.2K 3月 9 12:31 home.html
-rw-r--r-- 1 wangkan wangkan 1.2K 3月 9 12:31 hub.go
-rw-r--r-- 1 wangkan wangkan 884 3月 9 12:31 main.go
-rw-r--r-- 1 wangkan wangkan 4.7K 3月 9 12:31 README.md
按照官方的运行方式来运行这个项目
$ go run *.go
在浏览器中打开8080端口,可以看到该项目可以被成功运行了。
gorilla.png
就是这样一个简单的demo。
然后我们去看一下它的具体实现。
在这个项目中首先定义了一个hub的结构体:
type Hub struct {
// Registered clients.
clients map[*Client]bool
// Inbound messages from the clients.
broadcast chan []byte
// Register requests from the clients.
register chan *Client
// Unregister requests from clients.
unregister chan *Client
}
这个结构体中,clients代表所有已经注册的用户,broadcast管道会存储客户端发送来的信息。 register是一个*Client类型的管道,用于存储新注册的用户,unregister管道反之。
我们打开main.go,main函数的源码为:
func serveHome(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL)
if r.URL.Path != "/" {
http.Error(w, "Not found", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
http.ServeFile(w, r, "home.html")
}
func main() {
flag.Parse()
hub := newHub()
go hub.run()
http.HandleFunc("/", serveHome)
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(hub, w, r)
})
err := http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
在这里首先会新开一个goroutine,去跑hub的run方法,run方法中一个死循环,不停地去轮询hub中的内容
func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
//循环所有的client,并给他们的send中写入数据。
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
如果取到了新用户,就加入到clients中,如果取到了信息,就循环所有的client,将信息写到client.send中。
我们看到在请求路径为根的时候,它会请求一个函数,而这个函数就是将home.html发送到客户端。
而在请求路径为“/ws”的时候,他会执行一个serveWS的函数。
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
fmt.Println(*hub)
if err != nil {
log.Println(err)
return
}
client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
//将client写入到当前client.hub.register中
client.hub.register <- client
// Allow collection of memory referenced by the caller by doing all work in
// new goroutines.
go client.writePump()
go client.readPump()
}
每当一个新的用户进来之后,首先将连接升级为长连接,然后将当前的client写到register中,由hub.run函数去做处理。然后开启两个goroutine,一个去读client中发送来的数据,一个将数据写入到所有的client中,去发送给用户。
这就是整个聊天室的实现原理。
网友评论