使用 Go 和 WebSockets 構建實時聊天服務器

Nev5970 7年前發布 | 57K 次閱讀 WebSocket WebSocket 開發

現代網頁應用程序正日趨豐富而復雜。像這樣有趣又有活力的體驗很受用戶歡迎。用戶無需向服務器發起調用,或刷新瀏覽器,就可以讓頁面實時更新。早期的開發者依賴 AJAX 來創建具備近乎實時體驗的應用程序。而現在,他們運用 WebSockets 就能創建完全實時的應用程序了。

本教程中我們將使用 Go 編程語言以及 WebSockets 來創建一個實時的聊天應用程序。前端將會使用 HTML5 和 VueJS 來編寫。該內容需要你對 Go 語言, JavaScript 以及 HTML5 有一個基礎的了解,最好有一點點使用 VueJS 的經驗。

WebSocket 是什么?

通常 Web 應用使用一個或多個請求對 HTTP 服務器提供對外服務。客戶端軟件通常是 Web 瀏覽器向服務器發送請求,服務器發回一個響應。響應通常是 HTML 內容,由瀏覽器來渲染為頁面。樣式表,JavaScript 代碼和圖像也可以在響應中發送回來以完成整個網頁。每個請求和響應都屬于特定的單獨的連接的一部分,像 非死book 這樣的大型網站為了渲染單個頁面實際上可以產生數百個這樣的連接。

AJAX 的工作方式跟這個完全相同。使用 JavaScript,開發人員可以向 HTTP 服務器請求一小段信息,然后根據響應更新部分頁面。這可以在不刷新瀏覽器的情況下完成,但仍然存在一些限制。

每個 HTTP 請求/響應的連接在被響應之后都會關閉,因此獲得任何新的信息必須新建另一個連接。如果沒有新的請求發送給服務器,它就不知道客戶端正在查找新的信息。能讓 AJAX 應用程序看起來像實時的一種技術是定時循環發送 AJAX 請求。在設置了時間間隔之后,應用程序可以重新將請求發送到服務器,以查看是否有任何更新需要反饋給瀏覽器。這比較適合小型應用程序,但并不高效。這時候 WebSockets 就派上用場了。

WebSockets 是由 Internet 工程任務組(IETF)創建的建議標準的一部分。 RFC6455 中詳細描述了 WebSockets 實現的完整技術規范。下面是該文檔定義 WebSocket 的節選:

WebSocket 協議用于客戶端代碼和遠程主機之間進行通信,其中客戶端代碼是在可控環境下的非授信代碼

換句話說,WebSocket 是一個總是打開的連接,允許客戶端和服務器自發地來回發送消息。服務器可在必要時將新信息推送到客戶端,客戶端也可以對服務器執行相同操作。

JavaScript 中的 WebSockets

大多數現代瀏覽器 都在其 JavaScript 實現中支持 WebSockets。要從瀏覽器中啟動一個 WebSocket 連接,你可以使用簡單的 WebSocket JavaScript 對象,如下:

var ws = new Websocket("ws://example.com/ws");

您唯一需要的參數是一個 URL,WebSocket 連接可通過此 URL 連接服務器。該請求實際是一個 HTTP 請求,但為了安全連接我們使用“ws://”或“wss://”。這使服務器知道我們正在嘗試創建一個新的 WebSocket 連接。之后服務器將“升級”該客戶端和服務之間的連接到永久的雙向連接。

一旦新的 WebSocket 對象被創建,并且連接成功創建之后,我們就可以使用“send()”方法發送文本到服務器,并在 WebSocket 的“onmessage”屬性上定義一個處理函數來處理從服務器發送的消息。具體邏輯會在之后的聊天應用程序代碼中解釋。

Go 中的 WebSockets

WebSockets 并不包含在 Go 標準庫中,但幸運的是有一些不錯的第三方包讓 WebSockets 的使用輕而易舉。在這個例子中,我們將使用一個名為“gorilla/websocket”的包,它是流行的 Gorilla Toolkit 包集合的一部分,多用于在 Go 中創建 Web 應用程序。請運行以下命令進行安裝:

$ go get github.com/gorilla/websocket

構建服務器

這個應用程序的第一部分是服務器。這是一個處理請求的簡單 HTTP 服務器。它將為我們提供 HTML5 和 JavaScript 代碼,以及建立客戶端的 WebSocket 連接。另外,服務器還將跟蹤每個 WebSocket 連接并通過 WebSocket 連接將聊天信息從一個客戶端發送到所有其他客戶端。首先創建一個新的空目錄,然后在該目錄中創建一個“src”和“public”目錄。在“src”目錄中創建一個名為“main.go”的文件。

搭建服務器首先要進行一些設置。我們像所有 Go 應用程序一樣啟動應用程序,并定義包命名空間,在本例中為“main”。接下來我們導入一些有用的包。 “log”和“net/http”都是標準庫的一部分,將用于日志記錄并創建一個簡單的 HTTP 服務器。最終包“github.com/gorilla/websocket”將幫助我們輕松創建和使用 WebSocket 連接。

package mainimport (
        "log"
        "net/http"

        "github.com/gorilla/websocket")

下面的兩行代碼是一些全局變量,在應用程序的其它地方會被用到。全局變量的實踐較差,不過這次為了簡單起見我們還是使用了它們。第一個變量是一個 map 映射,其鍵對應是一個指向 WebSocket 的指針,其值就是一個布爾值。我們實際上并不需要這個值,但使用的映射數據結構需要有一個映射值,這樣做更容易添加和刪除單項。

第二個變量是一個用于由客戶端發送消息的隊列,扮演通道的角色。在后面的代碼中,我們會定義一個 goroutine 來從這個通道讀取新消息,然后將它們發送給其它連接到服務器的客戶端。

var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message)           // broadcast channel

接下來我們創建一個 upgrader 的實例。這只是一個對象,它具備一些方法,這些方法可以獲取一個普通 HTTP 鏈接然后將其升級成一個 WebSocket,稍后會有相關代碼介紹。

// Configure the upgrader
var upgrader = websocket.Upgrader{}

最后我們將定義一個對象來管理消息,數據結構比較簡單,帶有一些字符串屬性,一個 email 地址,一個用戶名以及實際的消息內容。我們將利用 email 來展示 Gravatar 服務所提供的唯一身份標識。

如果從 socket 中讀取數據有誤,我們假設客戶端已經因為某種原因斷開。我們記錄錯誤并從全局的 “clients” 映射表里刪除該客戶端,這樣一來,我們不會繼續嘗試與其通信。

另外,HTTP 路由處理函數已經被作為 goroutines 運行。這使得 HTTP 服務器無需等待另一個連接完成,就能處理多個傳入連接。

func handleConnections(w http.ResponseWriter, r *http.Request) {
    ...
        for {
                var msg Message                // Read in a new message as JSON and map it to a Message object
                err := ws.ReadJSON(&msg)
                if err != nil {
                        log.Printf("error: %v", err)
                        delete(clients, ws)
                        break
                }
                // Send the newly received message to the broadcast channel
                broadcast <- msg        }
}

服務器的最后一部分是 "handleMessages()"

“clients” 映射

func handleMessages() {
        for {
                // Grab the next message from the broadcast channel
                msg := <-broadcast
                // Send it out to every client that is currently connected
                for client := range clients {
                        err := client.WriteJSON(msg)
                        if err != nil {
                                log.Printf("error: %v", err)
                                client.Close()
                                delete(clients, client)
                        }
                }
        }
}

 

來自:https://www.oschina.net/translate/build-a-realtime-chat-server-with-go-and-websockets

 

 本文由用戶 Nev5970 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!