教你使用Websockets和Go編程語言構(gòu)建實時聊天應(yīng)用程序

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

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

WebSocket 是什么?

通常 Web 應(yīng)用使用一個或多個請求對 HTTP 服務(wù)器提供對外服務(wù)??蛻舳塑浖ǔJ?Web 瀏覽器向服務(wù)器發(fā)送請求,服務(wù)器發(fā)回一個響應(yīng)。響應(yīng)通常是 HTML 內(nèi)容,由瀏覽器來渲染為頁面。樣式表,JavaScript 代碼和圖像也可以在響應(yīng)中發(fā)送回來以完成整個網(wǎng)頁。每個請求和響應(yīng)都屬于特定的單獨的連接的一部分,像 Facebook 這樣的大型網(wǎng)站為了渲染單個頁面實際上可以產(chǎn)生數(shù)百個這樣的連接。

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

每個 HTTP 請求/響應(yīng)的連接在被響應(yīng)之后都會關(guān)閉,因此獲得任何新的信息必須新建另一個連接。如果沒有新的請求發(fā)送給服務(wù)器,它就不知道客戶端正在查找新的信息。能讓 AJAX 應(yīng)用程序看起來像實時的一種技術(shù)是定時循環(huán)發(fā)送 AJAX 請求。在設(shè)置了時間間隔之后,應(yīng)用程序可以重新將請求發(fā)送到服務(wù)器,以查看是否有任何更新需要反饋給瀏覽器。這比較適合小型應(yīng)用程序,但并不高效。這時候 WebSockets 就派上用場了。WebSockets 是由 Internet 工程任務(wù)組(IETF)創(chuàng)建的建議標準的一部分。 RFC6455 中詳細描述了 WebSockets 實現(xiàn)的完整技術(shù)規(guī)范。下面是該文檔定義 WebSocket 的節(jié)選:

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

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

JavaScript 中的 WebSockets

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

您唯一需要的參數(shù)是一個 URL,WebSocket 連接可通過此 URL 連接服務(wù)器。該請求實際是一個 HTTP 請求,但為了安全連接我們使用“ws://”或“wss://”。這使服務(wù)器知道我們正在嘗試創(chuàng)建一個新的 WebSocket 連接。之后服務(wù)器將“升級”該客戶端和服務(wù)之間的連接到永久的雙向連接。一旦新的 WebSocket 對象被創(chuàng)建,并且連接成功創(chuàng)建之后,我們就可以使用“send()”方法發(fā)送文本到服務(wù)器,并在 WebSocket 的“onmessage”屬性上定義一個處理函數(shù)來處理從服務(wù)器發(fā)送的消息。具體邏輯會在之后的聊天應(yīng)用程序代碼中解釋。

Go 中的 WebSockets

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

構(gòu)建服務(wù)器

這個應(yīng)用程序的第一部分是服務(wù)器。這是一個處理請求的簡單 HTTP 服務(wù)器。它將為我們提供 HTML5 和 JavaScript 代碼,以及建立客戶端的 WebSocket 連接。另外,服務(wù)器還將跟蹤每個 WebSocket 連接并通過 WebSocket 連接將聊天信息從一個客戶端發(fā)送到所有其他客戶端。首先創(chuàng)建一個新的空目錄,然后在該目錄中創(chuàng)建一個“src”和“public”目錄。在“src”目錄中創(chuàng)建一個名為“main.go”的文件。搭建服務(wù)器首先要進行一些設(shè)置。我們像所有 Go 應(yīng)用程序一樣啟動應(yīng)用程序,并定義包命名空間,在本例中為“main”。接下來我們導(dǎo)入一些有用的包。 “l(fā)og”和“net/http”都是標準庫的一部分,將用于日志記錄并創(chuàng)建一個簡單的 HTTP 服務(wù)器。最終包“http://github.com/gorilla/websocket”將幫助我們輕松創(chuàng)建和使用 WebSocket 連接。

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

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

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

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

由反引號包含的文本是 Go 在對象和 JSON 之間進行序列化和反序列化時需要的元數(shù)據(jù)。

Go 應(yīng)用程序的主要入口總是 "main()" 函數(shù)。代碼非常簡潔。我們首先創(chuàng)建一個靜態(tài)的文件服務(wù),并將之與 "/" 路由綁定,這樣用戶訪問網(wǎng)站時就能看到 index.html 和其它資源。在這個示例中我們有一個保存 JavaScript 代碼的 "app.js" 文件和一個保存樣式的 "style.css" 文件。

我們想定義的下一個路由是 "/ws",在這里處理啟動 WebSocket 的請求。我們先向處理函數(shù)傳遞一個函數(shù)的名稱,"handleConnections",稍后再來定義這個函數(shù)。

下一步就是啟動一個叫 "handleMessages" 的 Go 程序。這是一個并行過程,獨立于應(yīng)用和其它部分運行,從廣播頻道中取得消息并通過各客戶端的 WebSocket 連接傳遞出去。并行是 Go 中一項強大的特性。關(guān)于它如何工作的內(nèi)容超出了這篇文章的范圍,不過你可以自行查看 Go 的官方教程網(wǎng)站。如果你熟悉 JavaScript,可聯(lián)想一下并行過程,作為后臺過程運行的 Go 程序,或 JavaScript 的異步函數(shù)。

最后,我們向控制臺打印一個輔助信息并啟動 Web 服務(wù)。如果有錯誤發(fā)生,我們就把它記錄下來然后退出應(yīng)用程序。

接下來我們創(chuàng)建一個函數(shù)處理傳入的 WebSocket 連接。首先我們使用升級的 "Upgrade()" 方法改變初始的 GET 請求,使之成為完全的 WebSocket。如果發(fā)生錯誤,記錄下來,但不退出。同時注意 defer 語句,它通知 Go 在函數(shù)返回的時候關(guān)閉 WebSocket。這是個不錯的方法,它為我們節(jié)省了不少可能出現(xiàn)在不同分支中返回函數(shù)前的 "Close()" 語句。

接下來把新的客戶端添加到全局的 "clients" 映射表中進行注冊,這個映射表在早先已經(jīng)創(chuàng)建了。

最后一步是一個無限循環(huán),它一直等待著要寫入 WebSocket 的新消息,將其從 JSON 反序列化為 Message 對象然后送入廣播頻道。然而 "handleMessages()" Go 程序就能把它送給連接中的其它客戶端。

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

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

服務(wù)器的最后一部分是 "handleMessages()"函數(shù)。這是一個簡單循環(huán),從“broadcast”中連續(xù)讀取數(shù)據(jù),然后通過各自的 WebSocket 連接將消息傳播到所以客戶端。同樣,如果寫入 Websocket 時出現(xiàn)錯誤,我們將關(guān)閉連接,并將其從“clients” 映射中刪除。

構(gòu)建客戶端

如果沒有漂亮的 UI,聊天應(yīng)用程序?qū)o法完成。 我們需要使用一些 HTML5 和 VueJS 來創(chuàng)建一個簡單、干凈的界面,再利用一些諸如 Materialize CSS 和 EmojiOn 的庫來生成一些樣式和表情符號。 在“public”目錄中,創(chuàng)建一個名為“index.html”的新文件。第一部分很基礎(chǔ)。為了美觀,我們也會放入一些樣式表和字體?!皊tyle.css”是自定義的樣式表,用于自定義一些內(nèi)容。

下一部分僅與接口相關(guān),其中只包含一些用于選擇用戶名、發(fā)送消息和顯示新的聊天信息的字段。與 VueJS 交互的細節(jié)超出本文的介紹范圍,你可閱讀此文檔了解更多。

最后一步只需要導(dǎo)入所有需要的 JavaScript 庫,包括 Vue、EmojiOne、jQuery 和 Materialize。我們需要 MD5 庫獲取來自 Gravatar 的頭像 URL,這用 JavaScript 代碼寫出來就好理解了。最后導(dǎo)入 "app.js"。

然后在 "public" 目錄下創(chuàng)建一個 "style.css" 文件。其中會放入一些樣式。

客戶端的最后一部分是 JavaScript 代碼。在 "public" 目錄下創(chuàng)建文件 "app.js"。

對于 VueJS 應(yīng)用程序來說,一開始都是創(chuàng)建新的 Vue 對象。我們將它與 id 為 "#app" 的 div 綁定。這會讓 div 內(nèi)的所有東西與 Vue 實現(xiàn)共享作用域。下面定義一些變量。

Vue 提供了一個叫 "created" 的屬性,這是一個函數(shù),會在 Vue 實例剛剛創(chuàng)建時調(diào)用。這里非常適合對應(yīng)用做一些設(shè)置工作。在這個示例中我們希望創(chuàng)建一個新的 WebSocket 連接與服務(wù)器連接,并創(chuàng)建一個處理器用于處理從服務(wù)器發(fā)送過來的消息。我們把新的 WebSocket 對象保存在 "data" 屬性的 "ws" 變量中。

"addEventListener()"方法接受一個用于處理傳入消息的函數(shù)。我們期望所有消息都是 JSON 字符串,以便統(tǒng)一解析為一個對象字面量。然后我們可以用各個屬性和 avater 頭像一起組成漂亮的消息行。"gravatarURL()" 方法會在后面詳述。我們用了一個叫 EmojiOne 的表情庫來解析emoji 代碼。"toImage()" 方法會把 emoji 代碼轉(zhuǎn)換為實際的圖片。比如,如果你輸入 ":robot:",它會被替換為一個機器人 emoji 表情圖。

"methods" 屬性可以定義各種函數(shù),我們會在 VueJS 應(yīng)用中使用這些函數(shù)。"send"方法用于向服務(wù)器發(fā)送消息。我們先確保消息不是空的,然后把消息組織成一個對象,再用"stringify"把它變成 JSON 字符串,以便服務(wù)器能正確解析。我們使用 jQuery 來處理傳入消息中 HTML 和 JavaScript 中的特殊字符,以防止各種類型的注入攻擊。

"join"函數(shù)會確保用戶在發(fā)送消息前輸入 email 地址和用戶名。一旦他們輸入了這些信息,我們將 joined 設(shè)置為 "true",同時允許他們開始交談。同樣,我們會處理 HTML 和 JavaScript 的特殊字符。

最后一個函數(shù)是一個很好的輔助函數(shù),用于從 Gravatar 獲取頭像。URL 的最后一段需要用戶的 email 地址的 MD5 編碼。MD5 是一種加密算法,它能隱藏 email 地址同時還能讓 email 地址作為一個唯一標識來使用。

運行應(yīng)用程序

要運行該應(yīng)用程序,請打開控制臺窗口并確保進入應(yīng)用程序的“src”目錄中,然后運行以下命令。

接下來打開 Web 瀏覽器并導(dǎo)航到“http://localhost:8000”站點。 然后就會顯示聊天屏幕,你可以在聊天屏幕中輸入電子郵件和用戶名。

如果要查看該應(yīng)用多個用戶之間的通信方式,只需另外打開一個瀏覽器標簽頁或窗口,然后導(dǎo)航到“http://localhost:8000”。 輸入不同的電子郵件和用戶名。然后輪流從兩個窗口發(fā)送消息,這樣就可以看到多個用戶之間的通信方式了。

結(jié)論

這只是一個基本的聊天應(yīng)用程序,你可以在此基礎(chǔ)上進行更多的改進,添加更多的其他功能,并上傳源代碼,期待你能實現(xiàn)新用戶加入或者離開聊天時的私人提醒或者通知。盡情創(chuàng)造吧,此處不設(shè)限!我希望這篇文章對你有所幫助,并希望借此啟發(fā)你使用 WebSockets 和 Go 開始創(chuàng)建自己的實時應(yīng)用程序。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容