nodejs消息服务器

最近在写的项目中存在着社交模块,需要实现这样的一个功能:当数据有变更,需要由服务器向用户实时地推送一条消息。最终完成的项目地址为:socket-message-push,这里将介绍一下实现的思路及部分代码。

项目的流程中存在着这样的几个对象:

  • 用 Java 实现的后端服务器

  • 用 Node.js 实现的消息推送服务器

  • 用户进行操作的客户端

事件处理的流程如下:

  1. 用户进行贷款审批或放款,后端服务器会进行处理,并向 Node.js 消息推送服务器发送一条消息

  2. Node.js 消息推送服务器接收到后端发送的消息后,处理数据,并确定向哪个用户进行推送

  3. 用户的客户端接收到由 Node.js 服务器推送来的消息后,即可进行通知的显示。

考虑消息推送服务器上必须记录下当前在线用户的信息,这样才能向特定的用户推送消息。所以当用户登录时,必须将自身的用户信息发到 Node.js 服务器上。为了达到这种双向的实时消息传递,很明显地考虑用 WebSocket 来实现。既然我们在消息推送服务器上使用了 Node.js,我们就有了一个很方便的选项:socket.io。

Socket.io 介绍

Socket.io是一个用 Java 实现的实时双向通信的库,利用它来实现我们的功能会很简单。

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 消息推送服务器,先利用一个简要的例子来浏览其功能:

1.  // server.js

2.  constexpress = require('express');

3.  constapp = express();

4.  constpath = require('path');

5.  consthttp = require('http').Server(app);

7.  constport = 4001;

9.  app.use(express.static(path.join(__dirname, 'public')));

11.  app.get('/', function(req, res) {

12.  res.sendFile(__dirname + '/public/index.html');

13.  });

15.  app.get('/api', function(req, res) {

16.  res.send('.');

17.  });

19.  http.listen(port, function() {

20.  console.log(`listening on port:${port}`);

21.  });

将上面的代码保存为 server.js,新建一个 public文件夹,在其中放入 index.html文件。运行以下命令:

  1. node server.js

现在即可在 localhost:4001查看效果了。

引入 Socket.io

现在已经有了一个基础的 Express 服务器,接下来需要将 Socket.io 加入其中。

1.  constio = require('socket.io')(http);

3.  io.on('connection', function(socket) {

4.  console.log('a user connected');

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

6.  }

这里的 io监听 connection事件,当 client与 server建立了连接之后,这里的回调函数会被调用( client中的代码将在下一节介绍)。

函数的参数 socket代表的是当前的 client和 server间建立的这个连接。可在 client程序中将这个建立的 socket 连接打印出来,如下图所示:

image

其中的 id属性可以用于标识出这一连接,从而 server可以向特定的用户发送消息。

  1. 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 来向用户推送消息。

登录后建立socket连接

对用户信息的处理

方便起见,这里只用一个数组保存用户信息,实际工作中可以根据需要放入数据库中保存。

global.users = []; // 记录下登录用户的tokenId, socketId

当用户登录时, client会向 server发送 user_login事件,服务器接收到后会做如下操作:

1.  socket.on('user_login', function(info) {

2.  const{ tokenId, userId, socketId } = info;

3.  addSocketId(users, { tokenId, socketId, userId });

4.  });

addSocketId()会向 users数组中添加用户信息,不同用户通过 tokenId 进行区分,每个用户有一个 socketIds数组,保存可能存在的多个 socketId。该函数的具体代码可见 src/utils.js文件。

同理,还有一个 deleteSocketId()函数用于删除用户信息,代码可见同一文件。

在获取了用户的 tokenId 之后,就需要找到对应的 socketId,然后向特定用户推送消息。

1.  // 只向 id = socketId 的这一连接发送消息

2.  io.sockets.to(socketId).emit('receive_message', {

3.  entityType,

4.  data

5.  });

服务器的思路大致如此,接下来介绍客户端中是如何进行相应的处理的。

客户端

Socket.io 的初始化

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

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

其它的引入方式:

  1. <src="/socket.io/socket.io.js"></>
1.  constio = require('socket.io-client');

2.  // or with import syntax

3.  importio from 'socket.io-client';

引入 Socket.io 后就获得了 io函数,通过它来与消息推送服务器建立连接。

1.  // 假设你将 Node 服务器部署后的地址为:https://www.example.com/ws

2.  // 则: WS_HOST = 'https://www.example.com'

3.  constmsgSocket = io(`${WS_HOST}`, {

4.  secure: true,

5.  path: '/ws/socket.io'

6.  });

如果监听本地:

  1. constmsgSocket = io('http://localhost:4001');

这里如果写成 io('https://www.example.com/ws')会出现错误,需要将 /ws写入path中。

为了能在其它文件使用这一变量,可将 msgSocket作为一个全局变量:

  1. window.msgSocket = msgSocket;

用户建立连接

  1. // 用户登录时,向服务器发送用户的信息。服务器会在收到信息后建立 socket 与用户的映射。
2.  msgSocket.emit('user_login', {

3.  userId,

4.  socketId: msgSocket.id,

5.  tokenId

6.  });

接收到推送的消息后的处理

  1. // WebSocket 连接建立后,监听名为 receive_message 的事件
2.  msgSocket.on('receive_message', msg => {

3.  store.dispatch({

4.  type: 'NEW_SOCKET_MSG',

5.  payload: msg

6.  });

7.  });

当 WebSocket 服务器向客户端推送了消息之后,客户端需要监听 receive_message事件,接收到的参数中有相应待处理的信息。

由于使用了 Redux 进行数据的处理,所以这里 dispatch 了一个 NEW_SOCKET_MSGaction,后续则是常规的 redux 处理流程了。

项目的使用

GitHub 上的项目地址:

socket-message-push(https://github.com/noiron/socket-message-push

  1. npm run dev

即可在 devlopment 环境下进行测试,现在你就有了一个运行在4001端口的消息推送服务器了。

但是这里并没有后端的服务器来向我们发送消息,所以我们将利用 Postman 来模拟发送消息。

为了展示程序的功能,在项目的 client 文件夹下放置了一个 index.html文件。注意这个文件并不能用在实际的项目中,只是用来显示消息推送的效果而已。

在开启了服务器之后,打开 client/index.html,根据提示随意输入一个 tokenId 即可。

现在利用 Postman 向 localhost:4001/apipost 如下的一条信息:

{

// tokens 数组表示你想向哪个用户推送消息

"tokens": ["1", "2"],

"data": "You shall not pass!!!"

至此,如果一切顺利,你应该能够在 client 的控制台中看到收到的消息了。

你可以打开多个 client 页面,输入不同的 tokenId,然后检查消息是否发送给了正确的用户。

参考资料

https://github.com/socketio/socket.io/tree/master/examples/chat

https://socket.io/docs/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容