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