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