教你使用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ù)暇咆。客戶端軟件通常是 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)建的建議標(biāo)準(zhǔn)的一部分租冠。 RFC6455 中詳細(xì)描述了 WebSockets 實現(xiàn)的完整技術(shù)規(guī)范。下面是該文檔定義 WebSocket 的節(jié)選:

WebSocket 協(xié)議用于客戶端代碼和遠(yuǎn)程主機(jī)之間進(jìn)行通信薯嗤,其中客戶端代碼是在可控環(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 標(biāo)準(zhǔn)庫中,但幸運的是有一些不錯的第三方包讓 WebSockets 的使用輕而易舉缀去。在這個例子中侣灶,我們將使用一個名為“gorilla/websocket”的包,它是流行的 Gorilla Toolkit 包集合的一部分缕碎,多用于在 Go 中創(chuàng)建 Web 應(yīng)用程序褥影。請運行以下命令進(jìn)行安裝:

構(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ù)器首先要進(jìn)行一些設(shè)置。我們像所有 Go 應(yīng)用程序一樣啟動應(yīng)用程序注暗,并定義包命名空間坛缕,在本例中為“main”。接下來我們導(dǎo)入一些有用的包捆昏。 “l(fā)og”和“net/http”都是標(biāo)準(zhǔn)庫的一部分赚楚,將用于日志記錄并創(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ù)所提供的唯一身份標(biāo)識捣炬。

由反引號包含的文本是 Go 在對象和 JSON 之間進(jìn)行序列化和反序列化時需要的元數(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 中一項強(qiáng)大的特性。關(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ìn)行注冊慕嚷,這個映射表在早先已經(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ǔ)。為了美觀灰羽,我們也會放入一些樣式表和字體驮履。“style.css”是自定義的樣式表廉嚼,用于自定義一些內(nèi)容玫镐。

下一部分僅與接口相關(guān),其中只包含一些用于選擇用戶名前鹅、發(fā)送消息和顯示新的聊天信息的字段摘悴。與 VueJS 交互的細(xì)節(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:",它會被替換為一個機(jī)器人 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 地址作為一個唯一標(biāo)識來使用牲平。

運行應(yīng)用程序

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

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

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

結(jié)論

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碌冶,隨后出現(xiàn)的幾起案子湿痢,更是在濱河造成了極大的恐慌,老刑警劉巖扑庞,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件譬重,死亡現(xiàn)場離奇詭異,居然都是意外死亡罐氨,警方通過查閱死者的電腦和手機(jī)害幅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岂昭,“玉大人以现,你說我怎么就攤上這事狠怨。” “怎么了邑遏?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵佣赖,是天一觀的道長。 經(jīng)常有香客問我记盒,道長憎蛤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任纪吮,我火速辦了婚禮俩檬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碾盟。我一直安慰自己棚辽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布冰肴。 她就那樣靜靜地躺著屈藐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪熙尉。 梳的紋絲不亂的頭發(fā)上联逻,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音检痰,去河邊找鬼包归。 笑死,一個胖子當(dāng)著我的面吹牛铅歼,可吹牛的內(nèi)容都是我干的箫踩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼谭贪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锦担?” 一聲冷哼從身側(cè)響起俭识,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎洞渔,沒想到半個月后套媚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡磁椒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年堤瘤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浆熔。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡本辐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慎皱,我是刑警寧澤老虫,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站茫多,受9級特大地震影響祈匙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜天揖,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一夺欲、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧今膊,春花似錦些阅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赖钞,卻和暖如春腰素,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雪营。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工弓千, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人献起。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓洋访,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谴餐。 傳聞我的和親對象是個殘疾皇子姻政,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容