摘要:為了達(dá)到這種雙向的實(shí)時(shí)消息傳遞,很明顯地考慮用來實(shí)現(xiàn)。注意這個(gè)文件并不能用在實(shí)際的項(xiàng)目中,只是用來顯示消息推送的效果而已。參考資料本文在我博客上的原地址利用實(shí)現(xiàn)消息實(shí)時(shí)推送
項(xiàng)目背景介紹
最近在寫的項(xiàng)目中存在著社交模塊,需要實(shí)現(xiàn)這樣的一個(gè)功能:當(dāng)發(fā)生了用戶被點(diǎn)贊、評論、關(guān)注等操作時(shí),需要由服務(wù)器向用戶實(shí)時(shí)地推送一條消息。最終完成的項(xiàng)目地址為:socket-message-push,這里將介紹一下實(shí)現(xiàn)的思路及部分代碼。
項(xiàng)目的流程中存在著這樣的幾個(gè)對象:
用 Java 實(shí)現(xiàn)的后端服務(wù)器
用 Node.js 實(shí)現(xiàn)的消息推送服務(wù)器
用戶進(jìn)行操作的客戶端
事件處理的流程如下:
用戶進(jìn)行點(diǎn)贊操作時(shí),后端服務(wù)器會(huì)進(jìn)行處理,并向 Node.js 消息推送服務(wù)器發(fā)送一條消息
Node.js 消息推送服務(wù)器接收到后端發(fā)送的消息后,處理數(shù)據(jù),并確定向哪個(gè)用戶進(jìn)行推送
用戶的客戶端接收到由 Node.js 服務(wù)器推送來的消息后,即可進(jìn)行通知的顯示。
上面的流程中,Java 后端服務(wù)器是如何實(shí)現(xiàn)的不在此篇文章的討論范圍內(nèi),本文將主要介紹如何使用 Node.js 來實(shí)現(xiàn)這個(gè)消息推送服務(wù)器。
考慮消息推送服務(wù)器上必須記錄下當(dāng)前在線用戶的信息,這樣才能向特定的用戶推送消息。所以當(dāng)用戶登錄時(shí),必須將自身的用戶信息發(fā)到 Node.js 服務(wù)器上。為了達(dá)到這種雙向的實(shí)時(shí)消息傳遞,很明顯地考慮用 WebSocket 來實(shí)現(xiàn)。既然我們在消息推送服務(wù)器上使用了 Node.js,我們就有了一個(gè)很方便的選項(xiàng):socket.io。
Socket.io 介紹Socket.io是一個(gè)用 JavaScript 實(shí)現(xiàn)的實(shí)時(shí)雙向通信的庫,利用它來實(shí)現(xiàn)我們的功能會(huì)很簡單。
socket.io 包含兩個(gè)部分:
服務(wù)器端(server):運(yùn)行在 Node.js 服務(wù)器上
客戶端(client):運(yùn)行在瀏覽器中
可以看看如下的 socket.io 的示例代碼,它給出了 socket.io 發(fā)出及監(jiān)聽事件的基本用法:
io.on("connection", function(socket){ socket.emit("request", /* */); // emit an event to the socket io.emit("broadcast", /* */); // emit an event to all connected sockets socket.on("reply", function(){ /* */ }); // listen to the event });
關(guān)于 Socket.io 還有一點(diǎn)需要注意:Socke.io 并不完全是 WebSocket 的實(shí)現(xiàn)。
Note: Socket.IO is not a WebSocket implementation. Although Socket.IO indeed uses WebSocket as a transport when possible, it adds some metadata to each packet: the packet type, the namespace and the ack id when a message acknowledgement is needed.
接下來我們需要用 Express.js 來建立一個(gè)服務(wù)器端程序,并在其中引入 Socket.io。
Node.js 服務(wù)器的搭建 利用 Express.js 搭建基礎(chǔ)服務(wù)器我們使用了 Express.js 來搭建 Node.js 消息推送服務(wù)器,先利用一個(gè)簡要的例子來瀏覽其功能:
// server.js const express = require("express"); const app = express(); const path = require("path"); const http = require("http").Server(app); const port = 4001; app.use(express.static(path.join(__dirname, "public"))); app.get("/", function(req, res) { res.sendFile(__dirname + "/public/index.html"); }); app.get("/api", function(req, res) { res.send("."); }); http.listen(port, function() { console.log(`listening on port:${port}`); });
將上面的代碼保存為 server.js,新建一個(gè) public 文件夾,在其中放入 index.html 文件。運(yùn)行以下命令:
node server.js
現(xiàn)在即可在 localhost:4001 查看效果了。
引入 Socket.io現(xiàn)在已經(jīng)有了一個(gè)基礎(chǔ)的 Express 服務(wù)器,接下來需要將 Socket.io 加入其中。
const io = require("socket.io")(http); io.on("connection", function(socket) { console.log("a user connected"); socket.broadcast.emit("new_user", {}); }
這里的 io 監(jiān)聽 connection 事件,當(dāng) client 與 server 建立了連接之后,這里的回調(diào)函數(shù)會(huì)被調(diào)用(client 中的代碼將在下一節(jié)介紹)。
函數(shù)的參數(shù) socket 代表的是當(dāng)前的 client 和 server 間建立的這個(gè)連接。可在 client 程序中將這個(gè)建立的 socket 連接打印出來,如下圖所示:
其中的 id 屬性可以用于標(biāo)識出這一連接,從而 server 可以向特定的用戶發(fā)送消息。
socket.broadcast.emit("new_user", {});
這一行代碼表示 socket 將向當(dāng)前所有與 server 建立了連接的 client(不包括自己) 廣播一條名為 new_user 的消息。
后端推送消息的處理流程在 Node 服務(wù)器建立一個(gè)用戶信息和 socket id 的映射表,因?yàn)橥挥脩艨赡艽蜷_了多個(gè)頁面,所以他的 socket id 可能存在多個(gè)值。當(dāng)用戶建立連接時(shí),往其中添加值;用戶斷開連接后,刪除相應(yīng)值。
當(dāng) Java 后臺存在需要推送的消息時(shí),會(huì)向 Node 服務(wù)器的 /api 路徑 post 一條消息,其中包括用于標(biāo)識用戶的 tokenId 和其它數(shù)據(jù)。
Node 服務(wù)器接收到 post 請求后,對請求內(nèi)容進(jìn)行處理。根據(jù) tokenId 找出與該用戶對應(yīng)的 socket id,socket.io 會(huì)根據(jù) id 來向用戶推送消息。
對用戶信息的處理方便起見,這里只用一個(gè)數(shù)組保存用戶信息,實(shí)際工作中可以根據(jù)需要放入數(shù)據(jù)庫中保存。
global.users = []; // 記錄下登錄用戶的tokenId, socketId
當(dāng)用戶登錄時(shí),client 會(huì)向 server 發(fā)送 user_login 事件,服務(wù)器接收到后會(huì)做如下操作:
socket.on("user_login", function(info) { const { tokenId, userId, socketId } = info; addSocketId(users, { tokenId, socketId, userId }); });
addSocketId() 會(huì)向 users 數(shù)組中添加用戶信息,不同用戶通過 tokenId 進(jìn)行區(qū)分,每個(gè)用戶有一個(gè) socketIds 數(shù)組,保存可能存在的多個(gè) socketId。該函數(shù)的具體代碼可見 src/utils.js 文件。
同理,還有一個(gè) deleteSocketId() 函數(shù)用于刪除用戶信息,代碼可見同一文件。
在獲取了用戶的 tokenId 之后,就需要找到對應(yīng)的 socketId,然后向特定用戶推送消息。
// 只向 id = socketId 的這一連接發(fā)送消息 io.sockets.to(socketId).emit("receive_message", { entityType, data });
服務(wù)器的思路大致如此,接下來介紹客戶端中是如何進(jìn)行相應(yīng)的處理的。
客戶端 Socket.io 的初始化首先在 html 文件中引入 Socket.io 的 client 端文件,例如通過 CDN 引入:
其它的引入方式:
const io = require("socket.io-client"); // or with import syntax import io from "socket.io-client";
引入 Socket.io 后就獲得了 io 函數(shù),通過它來與消息推送服務(wù)器建立連接。
// 假設(shè)你將 Node 服務(wù)器部署后的地址為:https://www.example.com/ws // 則: WS_HOST = "https://www.example.com" const msgSocket = io(`${WS_HOST}`, { secure: true, path: "/ws/socket.io" });
如果監(jiān)聽本地:
const msgSocket = io("http://localhost:4001");
這里如果寫成 io("https://www.example.com/ws") 會(huì)出現(xiàn)錯(cuò)誤,需要將 /ws 寫入path中。
為了能在其它文件使用這一變量,可將 msgSocket 作為一個(gè)全局變量:
window.msgSocket = msgSocket;用戶建立連接
// 用戶登錄時(shí),向服務(wù)器發(fā)送用戶的信息。服務(wù)器會(huì)在收到信息后建立 socket 與用戶的映射。 msgSocket.emit("user_login", { userId, socketId: msgSocket.id, tokenId });接收到推送的消息后的處理
// WebSocket 連接建立后,監(jiān)聽名為 receive_message 的事件 msgSocket.on("receive_message", msg => { store.dispatch({ type: "NEW_SOCKET_MSG", payload: msg }); });
當(dāng) WebSocket 服務(wù)器向客戶端推送了消息之后,客戶端需要監(jiān)聽 receive_message 事件,接收到的參數(shù)中有相應(yīng)待處理的信息。
由于使用了 Redux 進(jìn)行數(shù)據(jù)的處理,所以這里 dispatch 了一個(gè) NEW_SOCKET_MSG action,后續(xù)則是常規(guī)的 redux 處理流程了。
項(xiàng)目的使用GitHub 上的項(xiàng)目地址:socket-message-push
npm run dev
即可在 devlopment 環(huán)境下進(jìn)行測試,現(xiàn)在你就有了一個(gè)運(yùn)行在4001端口的消息推送服務(wù)器了。
但是這里并沒有后端的服務(wù)器來向我們發(fā)送消息,所以我們將利用 Postman 來模擬發(fā)送消息。
為了展示程序的功能,在項(xiàng)目的 client 文件夾下放置了一個(gè) index.html 文件。注意這個(gè)文件并不能用在實(shí)際的項(xiàng)目中,只是用來顯示消息推送的效果而已。
在開啟了服務(wù)器之后,打開 client/index.html,根據(jù)提示隨意輸入一個(gè) tokenId 即可。
現(xiàn)在利用 Postman 向 localhost:4001/api post 如下的一條信息:
{ // tokens 數(shù)組表示你想向哪個(gè)用戶推送消息 "tokens": ["1", "2"], "data": "You shall not pass!!!" }
至此,如果一切順利,你應(yīng)該能夠在 client 的控制臺中看到收到的消息了。
你可以打開多個(gè) client 頁面,輸入不同的 tokenId,然后檢查消息是否發(fā)送給了正確的用戶。
參考資料https://github.com/socketio/s...
https://socket.io/docs/
本文在我博客上的原地址:利用 socket.io 實(shí)現(xiàn)消息實(shí)時(shí)推送
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85156.html
摘要:并且指定收到消息,以及端口的監(jiān)聽方法。四代碼示例多房間實(shí)時(shí)聊天室配置版本須在里配置定義,并設(shè)置。使同一個(gè)的請求能夠落在同一個(gè)機(jī)器同一個(gè)進(jìn)程中。通過主進(jìn)程統(tǒng)一管理維護(hù)子進(jìn)程,每個(gè)進(jìn)程監(jiān)聽一個(gè)端口。 showImg(http://7tszky.com1.z0.glb.clouddn.com/FkhApdRySR927nkdDZuUPBQbJtXG); 一、相關(guān)技術(shù)介紹: 消息實(shí)時(shí)推送,指的...
摘要:同時(shí)借助實(shí)現(xiàn)在非接口中推送消息流。每分秒鐘最多的彈幕數(shù)目彈幕數(shù)量過多時(shí)優(yōu)先加載最新的。 項(xiàng)目起始原因 源于數(shù)據(jù)庫課設(shè)和以前的一次突發(fā)奇想。其實(shí)還有其他微信公眾號的彈幕系統(tǒng),但是我發(fā)現(xiàn)使用體驗(yàn)不佳,因?yàn)槟欠N彈幕系統(tǒng)都是私用,并且只支持同時(shí)進(jìn)行一個(gè)房間的使用。所以便萌生了自己寫一個(gè)的想法。(第一次寫md,有點(diǎn)不會(huì),希望諒解--) 主要技術(shù)點(diǎn) Redis(結(jié)合socket實(shí)現(xiàn)在非socke...
摘要:目前網(wǎng)站有兩個(gè)用到實(shí)時(shí)消息推送的功能,源碼最新動(dòng)態(tài),實(shí)時(shí)顯示用戶的操作行為消息推送,如重要消息通知,任務(wù)指派等等考慮的問題既然要實(shí)現(xiàn)即時(shí),那就少不了。 目前網(wǎng)站有兩個(gè)用到實(shí)時(shí)消息推送的功能,源碼:https://github.com/wuzhc/team 最新動(dòng)態(tài),實(shí)時(shí)顯示用戶的操作行為 消息推送,如重要消息通知,任務(wù)指派等等 考慮的問題 既然要實(shí)現(xiàn)即時(shí),那就少不了socketi...
閱讀 3924·2021-11-24 09:38
閱讀 3106·2021-11-17 09:33
閱讀 3878·2021-11-10 11:48
閱讀 1244·2021-10-14 09:48
閱讀 3137·2019-08-30 13:14
閱讀 2554·2019-08-29 18:37
閱讀 3400·2019-08-29 12:38
閱讀 1422·2019-08-29 12:30