PHP开发PHP经验分享

go语言聊天室实现(二)gorilla/websocket中的聊

2020-03-15  本文已影响0人  公式般欢笑

我们可以看到 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中,去发送给用户。
这就是整个聊天室的实现原理。

上一篇下一篇

猜你喜欢

热点阅读