利用 socket.io 實現消息實時推送

項目背景介紹

最近在寫的項目中存在着社交模塊,須要實現這樣的一個功能:當發生了用戶被點贊、評論、關注等操做時,須要由服務器向用戶實時地推送一條消息。最終完成的項目地址爲:socket-message-push,這裏將介紹一下實現的思路及部分代碼。html

項目的流程中存在着這樣的幾個對象:node

  • 用 Java 實現的後端服務器git

  • 用 Node.js 實現的消息推送服務器github

  • 用戶進行操做的客戶端ajax

事件處理的流程以下:數據庫

  1. 用戶進行點贊操做時,後端服務器會進行處理,並向 Node.js 消息推送服務器發送一條消息express

  2. Node.js 消息推送服務器接收到後端發送的消息後,處理數據,並肯定向哪一個用戶進行推送npm

  3. 用戶的客戶端接收到由 Node.js 服務器推送來的消息後,便可進行通知的顯示。redux

上面的流程中,Java 後端服務器是如何實現的不在此篇文章的討論範圍內,本文將主要介紹如何使用 Node.js 來實現這個消息推送服務器。後端

考慮消息推送服務器上必須記錄下當前在線用戶的信息,這樣才能向特定的用戶推送消息。因此當用戶登陸時,必須將自身的用戶信息發到 Node.js 服務器上。爲了達到這種雙向的實時消息傳遞,很明顯地考慮用 WebSocket 來實現。既然咱們在消息推送服務器上使用了 Node.js,咱們就有了一個很方便的選項:socket.io。

Socket.io 介紹

Socket.io是一個用 JavaScript 實現的實時雙向通訊的庫,利用它來實現咱們的功能會很簡單。

socket.io 包含兩個部分:

  • 服務器端(server):運行在 Node.js 服務器上

  • 客戶端(client):運行在瀏覽器中

能夠看看以下的 socket.io 的示例代碼,它給出了 socket.io 發出及監聽事件的基本用法:

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
});

關於 Socket.io 還有一點須要注意:Socke.io 並不徹底是 WebSocket 的實現。

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 來創建一個服務器端程序,並在其中引入 Socket.io。

Node.js 服務器的搭建

利用 Express.js 搭建基礎服務器

咱們使用了 Express.js 來搭建 Node.js 消息推送服務器,先利用一個簡要的例子來瀏覽其功能:

// 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,新建一個 public 文件夾,在其中放入 index.html 文件。運行如下命令:

node server.js

如今便可在 localhost:4001 查看效果了。

引入 Socket.io

如今已經有了一個基礎的 Express 服務器,接下來須要將 Socket.io 加入其中。

const io = require('socket.io')(http);

io.on('connection', function(socket) {
    console.log('a user connected');
    socket.broadcast.emit('new_user', {});
}

這裏的 io 監聽 connection 事件,當 clientserver 創建了鏈接以後,這裏的回調函數會被調用(client 中的代碼將在下一節介紹)。

函數的參數 socket 表明的是當前的 clientserver 間創建的這個鏈接。可在 client 程序中將這個創建的 socket 鏈接打印出來,以下圖所示:

clipboard.png

其中的 id 屬性能夠用於標識出這一鏈接,從而 server 能夠向特定的用戶發送消息。

socket.broadcast.emit('new_user', {});

這一行代碼表示 socket 將向當前全部與 server 創建了鏈接的 client(不包括本身) 廣播一條名爲 new_user 的消息。

後端推送消息的處理流程

  1. 在 Node 服務器創建一個用戶信息和 socket id 的映射表,由於同一用戶可能打開了多個頁面,因此他的 socket id 可能存在多個值。當用戶創建鏈接時,往其中添加值;用戶斷開鏈接後,刪除相應值。

  2. 當 Java 後臺存在須要推送的消息時,會向 Node 服務器的 /api 路徑 post 一條消息,其中包括用於標識用戶的 tokenId 和其它數據。

  3. Node 服務器接收到 post 請求後,對請求內容進行處理。根據 tokenId 找出與該用戶對應的 socket id,socket.io 會根據 id 來向用戶推送消息。

對用戶信息的處理

方便起見,這裏只用一個數組保存用戶信息,實際工做中能夠根據須要放入數據庫中保存。

global.users = []; // 記錄下登陸用戶的tokenId, socketId

當用戶登陸時,client 會向 server 發送 user_login 事件,服務器接收到後會作以下操做:

socket.on('user_login', function(info) {
    const { tokenId, userId, socketId } = info;
    addSocketId(users, { tokenId, socketId, userId });
});

addSocketId() 會向 users 數組中添加用戶信息,不一樣用戶經過 tokenId 進行區分,每一個用戶有一個 socketIds 數組,保存可能存在的多個 socketId。該函數的具體代碼可見 src/utils.js 文件。

同理,還有一個 deleteSocketId() 函數用於刪除用戶信息,代碼可見同一文件。

在獲取了用戶的 tokenId 以後,就須要找到對應的 socketId,而後向特定用戶推送消息。

// 只向 id = socketId 的這一鏈接發送消息 
io.sockets.to(socketId).emit('receive_message', {
    entityType,
    data
});

服務器的思路大體如此,接下來介紹客戶端中是如何進行相應的處理的。

客戶端

Socket.io 的初始化

首先在 html 文件中引入 Socket.io 的 client 端文件,例如經過 CDN 引入:

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>

其它的引入方式:

<script src="/socket.io/socket.io.js"></script>
const io = require('socket.io-client');
// or with import syntax
import io from 'socket.io-client';

引入 Socket.io 後就得到了 io 函數,經過它來與消息推送服務器創建鏈接。

// 假設你將 Node 服務器部署後的地址爲:https://www.example.com/ws
// 則: WS_HOST = 'https://www.example.com'
const msgSocket = io(`${WS_HOST}`, {
    secure: true,
    path: '/ws/socket.io'
});

若是監聽本地:

const msgSocket = io('http://localhost:4001');

這裏若是寫成 io('https://www.example.com/ws') 會出現錯誤,須要將 /ws 寫入path中。

爲了能在其它文件使用這一變量,可將 msgSocket 做爲一個全局變量:

window.msgSocket = msgSocket;

用戶創建鏈接

// 用戶登陸時,向服務器發送用戶的信息。服務器會在收到信息後創建 socket 與用戶的映射。
msgSocket.emit('user_login', {
    userId,
    socketId: msgSocket.id,
    tokenId
});

接收到推送的消息後的處理

// WebSocket 鏈接創建後,監聽名爲 receive_message 的事件 
msgSocket.on('receive_message', msg => {
    store.dispatch({
        type: 'NEW_SOCKET_MSG',
        payload: msg
    });
});

當 WebSocket 服務器向客戶端推送了消息以後,客戶端須要監聽 receive_message 事件,接收到的參數中有相應待處理的信息。

因爲使用了 Redux 進行數據的處理,因此這裏 dispatch 了一個 NEW_SOCKET_MSG action,後續則是常規的 redux 處理流程了。

項目的使用

GitHub 上的項目地址:socket-message-push

npm run dev

便可在 devlopment 環境下進行測試,如今你就有了一個運行在4001端口的消息推送服務器了。

可是這裏並無後端的服務器來向咱們發送消息,因此咱們將利用 Postman 來模擬發送消息。

爲了展現程序的功能,在項目的 client 文件夾下放置了一個 index.html 文件。注意這個文件並不能用在實際的項目中,只是用來顯示消息推送的效果而已。

在開啓了服務器以後,打開 client/index.html,根據提示隨意輸入一個 tokenId 便可。

如今利用 Postman 向 localhost:4001/api post 以下的一條信息:

{ 
    // tokens 數組表示你想向哪一個用戶推送消息
    "tokens": ["1", "2"], 
    "data": "You shall not pass!!!"
}

postman-post-a-message

至此,若是一切順利,你應該可以在 client 的控制檯中看到收到的消息了。

client-message

你能夠打開多個 client 頁面,輸入不一樣的 tokenId,而後檢查消息是否發送給了正確的用戶。

參考資料

https://github.com/socketio/s...
https://socket.io/docs/


本文在我博客上的原地址:利用 socket.io 實現消息實時推送

相關文章
相關標籤/搜索