摘要:用偽代碼來模擬下長輪詢的過程前端利用下面函數(shù)進(jìn)行請求后端代碼做如下更改利用隨機(jī)數(shù)的大小來模擬是否有新數(shù)據(jù)有新數(shù)據(jù)來了長輪詢的確減少了請求的次數(shù),但是它也有著很大的問題,那就是耗費(fèi)服務(wù)器的資源。
寫在前面
最近由于利用node重構(gòu)某個(gè)項(xiàng)目,項(xiàng)目中有一個(gè)實(shí)時(shí)聊天的功能,于是就研究了一下聊天室,在線demo|源碼,歡迎大家反饋。這個(gè)聊天室的主要利用到了socket.io和express。這個(gè)聊天室支持群聊,私聊,支持發(fā)送圖片(PS:大家在體驗(yàn)時(shí)最好開啟兩個(gè)瀏覽器,自問自答)。下面就來和大家分享下實(shí)現(xiàn)過程:
WebSocketHTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信。
為了更好的理解WebSocket,需要了解一下在沒有WebSocket階段是如何寫聊天室這種實(shí)時(shí)系統(tǒng)的:
基于http協(xié)議瀏覽器可以實(shí)現(xiàn)單向通信,只能由瀏覽器發(fā)起請求(Request),服務(wù)器進(jìn)行響應(yīng)(Response),一個(gè)請求對應(yīng)一個(gè)響應(yīng)。由于服務(wù)器不能主動(dòng)向客戶端推送消息,于是普遍采用的方式就是輪詢(polling),輪詢實(shí)現(xiàn)起來非常簡單,就是定時(shí)的利用ajax向服務(wù)器端進(jìn)行請求。如果服務(wù)器有新的數(shù)據(jù)就返回新的數(shù)據(jù),如果沒有數(shù)據(jù)就返回空響應(yīng)。用代碼來模擬下就是這個(gè)樣子的:
// 前端請求代碼 function update (fn) { var xhr = new XMLHttpRequest(); xhr.open("get", "./update.php"); xhr.onreadystatechange = function(){ if(xhr.readyState === 4){ if(xhr.status == 200){ const res = JSON.parse(xhr.response); if (res.flag) { // 進(jìn)行相應(yīng)操作 // fn為接到響應(yīng)后的處理函數(shù) fn && fn(fn); } } } }; xhr.send(); } function polling () { update(); } setInterval(polling, 2000); // 后臺(tái)響應(yīng)代碼 true, "data" => "有新數(shù)據(jù)來了" )); } else { echo json_encode(array( "flag" => false )); } ?>
這種定時(shí)請求的方式的關(guān)鍵在于間隔時(shí)間的選取,依據(jù)我在上面代碼做的模擬,很少概率能拿到下真正的數(shù)據(jù),多半的ajax請求是無效的,于是又有前輩基于輪詢提出來了Comet(服務(wù)器推),這種技術(shù)可以通過長輪詢(long polling)實(shí)現(xiàn)(還可以利用iframe),長輪詢也是靠ajax實(shí)現(xiàn)客戶端的請求,其流程為:客戶端發(fā)起請求,服務(wù)器掛起請求,假若有新的數(shù)據(jù)返回,服務(wù)器響應(yīng)客戶端剛才的請求,客戶端得到響應(yīng)后繼續(xù)請求服務(wù)器。用偽代碼來模擬下長輪詢的過程:
// 前端利用下面函數(shù)進(jìn)行請求 function longPolling () { update(update); } longpolling(); // 后端代碼做如下更改 true, "data" => "有新數(shù)據(jù)來了" )); break; } } ?>
長輪詢的確減少了請求的次數(shù),但是它也有著很大的問題,那就是耗費(fèi)服務(wù)器的資源。
無論是輪詢還是長輪詢,還有著一個(gè)問題就是http并不是支持長連接很多人會(huì)說keep-alive不就是做到了長連接嗎?然而并非如此,keep-alive是重用一個(gè)TCP連接,就是說http 1.1做到了一個(gè)TCP連接可以發(fā)送多個(gè)http請求,然而每個(gè)http請求還需要發(fā)送Request Header,每個(gè)請求的響應(yīng)還會(huì)帶著Response Header。對于輪詢和長輪詢來說伴隨著真實(shí)數(shù)據(jù)的交換,還有進(jìn)行的就是大量的http header的交換。
基于這些問題,WebSocket被提出,WebSocket可以理解為對http的一個(gè)補(bǔ)丁包,WebSocket使http變成了一個(gè)真正的長連接,握手階段利用http協(xié)議,之后就不會(huì)再發(fā)起http請求了。下面來看下WebSocket握手的過程:
客戶端的請求頭比一般的http請求多出來幾個(gè)字段:
Upgrade: websocket,Connection: Upgrade,利用這兩個(gè)字段來告訴服務(wù)器,我要將協(xié)議升級為websocket。
Sec-WebSocket-Version: 13,來告訴服務(wù)器我想要使用的WebSocket的版本。
Sec-WebSocket-Key,其值采用base64編碼的隨機(jī)16字節(jié)長的字符序列,這個(gè)值會(huì)在響應(yīng)頭中回應(yīng)。
Sec-WebSocket-Extensions,提供了一個(gè)客戶端支持的協(xié)議擴(kuò)展列表來供服務(wù)器選擇,服務(wù)器只能選擇一個(gè),并且會(huì)將選擇的擴(kuò)展寫入響應(yīng)頭的Sec-WebSocket-Extensions。
Sec-WebSocket-Protocol,與Sec-WebSocket-Extensions原理相似,用于協(xié)商應(yīng)用子協(xié)議。
再來看看響應(yīng)頭:
Status Code,值為101,表示已經(jīng)升級到WebSocket協(xié)議
Sec-WebSocket-Extensions告訴客戶端服務(wù)器選擇的協(xié)議擴(kuò)展
Sec-WebSocket-Protocol告訴客戶端服務(wù)器選擇的子協(xié)議
Sec-WebSocket-Accept經(jīng)服務(wù)器確認(rèn)并且加密后的Sec-WebSocket-Key
還有一點(diǎn)值得關(guān)注的就是協(xié)議頭由http/https換成了ws/wss,也標(biāo)識(shí)真http完成了其使命,接下來的事情由WebSocket來負(fù)責(zé)啦!
socket.io由于寫原生的WebSocket在處理低版本瀏覽器的兼容性上的困難,所以一般在寫實(shí)時(shí)交互的這種項(xiàng)目時(shí)一般會(huì)利用到socket.io。socket.io并不僅僅是WebSocket,還包含著AJAX long polling,AJAX multipart streaming,JSONP Polling等。socket.io可以看做是基于engine.io的二次開發(fā)。通過emit和on可以輕松地實(shí)現(xiàn)服務(wù)器與客戶端之間的雙向通信,emit來發(fā)布事件,on來訂閱事件。
用戶登錄/登出下面開始來寫代碼,我利用的構(gòu)建工具是gulp,模板語言是jade,css預(yù)處理語言是less,假若也需要使用到這些,可以關(guān)注下我所在團(tuán)隊(duì)搭建的一個(gè)小的腳手架,先從app.js開始:
const users = {}, app = express(), server = require("http").createServer(app), io = require("socket.io").listen(server); // 將socket.io綁定到服務(wù)器上,使得任何連接到服務(wù)器的客戶端都具有實(shí)時(shí)通信的功能 // 服務(wù)器來監(jiān)聽客戶端 io.on("connection", (socket) => { // socket是返回的連接對象,兩端的交互就是通過這個(gè)對象 });
需要?jiǎng)?chuàng)建一個(gè)對象(users)來存儲(chǔ)在線用戶,鍵值為用戶昵稱,為用戶登錄來訂閱個(gè)事件:
socket.on("login", (nickname) => { if (users[nickname] || nickname === "system") { socket.emit("repeat"); } else { socket.nickname = nickname; users[nickname] = { name: nickname, socket: socket, lastSpeakTime: nowSecond() }; socket.emit("loginSuccess"); UsersChange(nickname, true); } }); socket.on("disconnect", () => { if (socket.nickname && users[socket.nickname]) { delete users[socket.nickname]; UsersChange(socket.nickname, false); } }); function UsersChange (nickname, flag) { io.sockets.emit("system", { nickname: nickname, size: Object.keys(users).length, flag: flag }); } function nowSecond () { return Math.floor(new Date() / 1000); }
用戶登錄時(shí)需要驗(yàn)證其昵稱是否含有,假若函數(shù),則觸發(fā)在客戶端的js代碼中注冊的repeat事件,反之觸發(fā)loginSuccess事件并且登錄成功后需要向所有的客戶端來廣播,所以利用了io.sockets.emit。repeat,loginSuccess,system,在src/js/index.js中進(jìn)行注冊,主要用于頁面的顯示,也就是一些dom操作,所以在這里沒有什么好講的。用戶退出,直接調(diào)用默認(rèn)事件disconnect就好,并將該用戶從用戶對象中移除。
心跳檢測在用戶的狀態(tài)上的坑還是不少的,因?yàn)?b>WebSocket中間過程比較復(fù)雜,經(jīng)常會(huì)出現(xiàn)一些異常的情況,所以需要進(jìn)行心跳檢測,我采用的方式是服務(wù)端定時(shí)遍歷用戶列表,假若用戶最后的發(fā)言時(shí)間與現(xiàn)在相比超過了5分鐘,就將其視為掉線,從而避免了"用戶undefined退出群聊"的這種情況。
function pong () { const now = nowSecond(); for (let k in users) { if (users[k].lastSpeakTime + MAX_LEAVE_TIME < now) { var socket = users[k].socket; users[k].socket.emit("disconnect"); socket.emit("nouser", "由于長時(shí)間未說話,您已經(jīng)掉線,請重新刷新頁面"); socket = null; } } } // 心跳檢測 setInterval(pong, PONG_TIME); function UsersChange (nickname, flag) { io.sockets.emit("system", { nickname: nickname, size: Object.keys(users).length, flag: flag }); }寫在最后
其實(shí)socket.io的使用真的非常簡單,很容易就會(huì)上手,所以其余功能不再一一演示,大家可以看代碼的實(shí)現(xiàn)(寫的比較差,還請見諒),客戶端代碼中大量用到了L,相當(dāng)于zepto的$,特別需要處理的是在私信和發(fā)送圖片的處理上,私信需要處理不同消息框,到底把消息添加到那個(gè)消息框中,我利用了一個(gè)對象來存儲(chǔ)這些信息(cache),cache的鍵名為用戶的昵稱(因?yàn)樵谧詴r(shí)判斷了其是否唯一,所以可以將其視為唯一的);鍵值為對象,對象屬性如下圖所示:
具體實(shí)現(xiàn)大家還是到源碼中去看吧!
感謝王哇勇大神的HiChat和小胡子哥的blogChat
由于本人水平有限,如有錯(cuò)誤,歡迎大家指出!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/91020.html
摘要:簡易版聊天室技術(shù)棧功能實(shí)現(xiàn)實(shí)時(shí)聊天創(chuàng)建房間表情包完善私聊效果登錄服務(wù)端判斷之前是否登錄過聊天室,如果是則直接進(jìn)入聊天室,否則跳轉(zhuǎn)到登錄頁面??蛻舳税l(fā)送創(chuàng)建房間和切換房間的事件給服務(wù)端。 Chat 簡易版聊天室 技術(shù)棧 express socket.io 功能 實(shí)現(xiàn) 實(shí)時(shí)聊天 創(chuàng)建房間 表情包 完善 私聊 效果 登錄 showImg(https://segmentfa...
摘要:項(xiàng)目簡介主要是通過做一個(gè)多人在線多房間群聊的小項(xiàng)目來練手全棧技術(shù)的結(jié)合運(yùn)用。編譯運(yùn)行開啟服務(wù),新建命令行窗口啟動(dòng)服務(wù)端,新建命令行窗口啟動(dòng)前端頁面然后在瀏覽器多個(gè)窗口打開,注冊不同賬號并登錄即可進(jìn)行多用戶多房間在線聊天。 項(xiàng)目簡介 主要是通過做一個(gè)多人在線多房間群聊的小項(xiàng)目、來練手全棧技術(shù)的結(jié)合運(yùn)用。 項(xiàng)目源碼:chat-vue-node 主要技術(shù): vue2全家桶 + socket....
摘要:云新聞云新聞收藏的使用需要注意的地方提交的是,而不是直接的狀態(tài)變更可以包含任意異步操作。的使用利用實(shí)現(xiàn)了簡單的聊天功能,在同一個(gè)服務(wù)器下。 title: Socket.io+vue打造新聞社區(qū)date: 2017-06-12 20:19:05 tags: [vue.js,javascript,socket.io] vue2.0 + socket.io打造一個(gè)DIY新聞社區(qū)(web第一...
摘要:異步最佳實(shí)踐避免回調(diào)地獄前端掘金本文涵蓋了處理異步操作的一些工具和技術(shù)和異步函數(shù)。 Nodejs 連接各種數(shù)據(jù)庫集合例子 - 后端 - 掘金Cassandra Module: cassandra-driver Installation ... 編寫 Node.js Rest API 的 10 個(gè)最佳實(shí)踐 - 前端 - 掘金全文共 6953 字,讀完需 8 分鐘,速讀需 2 分鐘。翻譯自...
摘要:但是需要注意的一點(diǎn)是協(xié)議是建立在協(xié)議基礎(chǔ)之上的,需要經(jīng)過一次握手。所以連接的發(fā)起方仍是客戶端。是一個(gè)簡潔而靈活的應(yīng)用框架提供一系列強(qiáng)大特性幫助你創(chuàng)建各種應(yīng)用。這也是為什么要采用協(xié)議來實(shí)現(xiàn)聊天室的原因。 從開始寫到完善差不多斷斷續(xù)續(xù)差不多半個(gè)月時(shí)間,雖然還沒有打到想要的效果但還是階段性總結(jié)一下。(下一步加入打算視頻通訊功能)本文默認(rèn)你已掌握 node 相關(guān)基礎(chǔ)知識(shí) GitHub地址:ht...
閱讀 3567·2021-11-25 09:43
閱讀 3144·2021-10-08 10:04
閱讀 1635·2019-08-26 12:20
閱讀 2067·2019-08-26 12:09
閱讀 608·2019-08-23 18:25
閱讀 3581·2019-08-23 17:54
閱讀 2336·2019-08-23 17:50
閱讀 813·2019-08-23 14:33