专栏首页Nodejs技术栈Node.js + Socket.io 实现一对一即时聊天

Node.js + Socket.io 实现一对一即时聊天

实现一对一即时聊天应用,重要的一点就是消息能够实时的传递,一种方案就是熟知的使用 Websocket 协议,本文中我们使用 Node.js 中的一个框架 Socket.io 来实现。

效果预览

先看下,我们实现的最终效果,如下所示:

你也可以在浏览器分别输入以下两个 URL 地址进行体验:

  • http://120.27.239.212:30010/?sender=赵敏&receiver=聂小倩
  • http://120.27.239.212:30010/?sender=聂小倩&receiver=赵敏

技术选型

  • 前端:HTML + CSS + JS 还用到了 Boostrap 来实现我们的页面布局和一些样式渲染。
  • 后端:Node.js + Express + Socket.io。

前端实现

HTML 页面布局

聊天页面的 HTML 布局是不复杂的,大体分为 3 层,如下所示:

  • chat-header:聊天界面头部信息。
  • chat-content:用来显示聊天的整体内容信息,现在看到的仅是一个空的 div 在发出或收到聊天信息之后会去操作 DOM 向聊天体内插入消息内容。
  • chat-bottom:最下面展示了我们聊天窗口的内容输入窗口和发送按钮。
 <div class="container">
   <div class="chat-header row">
    <span class="col-xs-2 chat-header-left glyphicon glyphicon-menu-left"></span>
    <span class="col-xs-8 chat-header-center" id="chatHeaderCenter"></span>
    <span class="col-xs-2 chat-header-right glyphicon glyphicon-option-horizontal"></span>
  </div>
  <div class="chat-content" id="chatContent"></div>
  <div class="chat-bottom row">
    <span class="col-xs-10 col-md-11 input-text"><input type="text" class="form-control " id="inputText" placeholder="请输入要发送的内容..."></span>
   <span class="col-xs-2 col-md-1 span-submit">
     <input class="btn btn-default btn-primary input-submit" id="sendBtn" data-dismiss="alert" type="submit" value="发送">
    </span>
  </div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="./js/chat.js"></script>

Socket.io Client

客户端首先创建一个 socket 对象,io() 的第一个参数是链接服务器的 URL,默认情况下是 window.location。 Socket 的客户端和服务端都有两个函数 on()、emit() 这也是核心,通过这两个函数可以轻松的实现客户端与服务端的双向通信。

  • emit:触发一个事件,第一个参数是事件名称,第二个参数是要发送到另一端的数据,第三个参数是一个回调函数用来确认对方的接收信息,这个可以忽略。
  • on:注册一个事件,用来监听 emit 触发的事件。
// js/chat.js
const socket = io();
socket.on('connect', () => {
  socket.emit('online', query.sender);
});
socket.on('reply_private_chat', replyPrivateMessage);
...

在客户端发送消息,则是监听发送按钮的 onclick 事件或回车事件,对消息做一些处理通过 socket.emit 发送到服务端,由服务端转接到另一客户端。

前端部分更多细节代码,这里不再列举,可在 Github 上 Clone 下来自行查看,文末有代码示例地址。

const chatHeaderCenter = document.getElementById('chatHeaderCenter');
const inputText = document.getElementById('inputText');
const sendBtn = document.getElementById('sendBtn');
chatHeaderCenter.innerText = query.receiver;
sendBtn.onclick = sendMsg;
inputText.onkeydown = sendMsgByEnter;

function sendMsg() {
  const value = inputText.value;
  if (!value) return alert('Message is required!');
  const message = { sender: query.sender, receiver: query.receiver, text: value };
  socket.emit('private_chat', message, data => {
    renderMessage(data, true);
  });
  inputText.value = '';
}
...

后端实现

使用 Express 搭建服务

使用 Express 搭建我们的后端服务,创建一个 app.js 里面监听 30010 端口,加载我们的客户端页面。

// app.js
const express = require('express');
const app = express();
const path = require('path');
const server = require('http').createServer(app);
const PORT = 30010;

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

server.listen(PORT, () => console.log(`Server is listening on ${PORT}`));

引入 Socket.io

上面我们已经搭建了一个简单的 Express 服务,现在引入我们自定义的 io.js。

// app.js
require('./io.js')(server);

创建 io.js 在加载 socket.io 时传入 server 对象,这时会拿到一个服务端的 io 对象,同步的注册 connection 事件,如果有新的客户端进来会被触发,connection 回调函数的 socket 是指当前客户端与服务端建立的链接。

还有 online、private_chat、disconnect 这些事件有些是系统提供的,有些是我们自定义的,下文还会在介绍。

const _ = require('underscore');
const moment = require('moment');
const userData = require('./users.json');
const USER_STATUS = ['ONLINE', 'OFFLINE'];
const users = {};

module.exports = server => {
  const io = require('socket.io')(server);

  io.on('connection', socket => {
    socket.on('online', ...)
    socket.on('private_chat', ...);
    socket.on('disconnect', ...);
  });
}

上线通知

on('online') 是我们自定义的事件,由客户端上线后触发告诉我们当前客户端的用户信息,保存 socket.id 建立用户与 socket.id 的映射关系,用于后续私聊。这里的 socket.id 每一次客户端断开重链都是会变的。

socket.on('online', username => {
  socket.username = username;
  users[username] = {
    socketId: socket.id,
    status: USER_STATUS[0]
  };
})

接收发送的私聊消息

on('private_chat') 也是我们自定义的事件,收到客户端发送的消息后对消息做处理,判断接收方是否在线,如果在线通过 socket.id 找到对应的 socket 向接收方推送消息,如果用户不在线,可以做些离线消息推送处理。这里私聊转发关键的一点是 socket.to().emit()。

socket.on('private_chat', (params, fn) => {
  const receiver = users[params.receiver];
  params.createTime = moment().format('YYYY-MM-DD HH:mm:ss');
  const senderData = _.findWhere(userData, { username: params.sender });
  params.senderPhoto = (senderData || {}).photo;

  if (!params.senderPhoto) {
    const senderLen = params.sender.length;
    params.senderPhotoNickname = params.sender.substr(senderLen - 2)
  }
  fn(params);
  if (receiver && receiver.status === USER_STATUS[0]) {
    socket.to(users[params.receiver].socketId).emit('reply_private_chat', params);
  } else {
    console.log(`${params.receiver} 不在线`);
    // 可以在做些离线消息推送处理
  }
});

disconnect

断开链接时触发,reason 表示客户端或服务端断开链接的原因。在这个事件里我们也会更改断开链接的原因。

socket.on('disconnect', reason => {
  if (users[socket.username]) users[socket.username].status = USER_STATUS[1];
});

代码&部署

我将以上示例打包为了一个 Docker 镜像,感兴趣的可以执行以下命令拉取,自行部署运行。

docker pull docker.io/qufei1993/private-chat-socketio

代码示例:

  • Github: https://github.com/qufei1993/Examples

Demo 在线体验:

  • http://120.27.239.212:30010/?sender=赵敏&receiver=聂小倩
  • http://120.27.239.212:30010/?sender=聂小倩&receiver=赵敏

总结

Socket.io 已经封装的很好了,使用它开发一个即时聊天应用更多工作需要我们去接入自己的业务逻辑,本文也只是一个聊天系统的冰山一角,还有很多需要去做,感兴趣的朋友欢迎关注,后续会持续分享一些其它功能。

本文分享自微信公众号 - Nodejs技术栈(NodejsRoadmap),作者:五月君

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Node.js 定时器详解

    只要用到引擎之外的功能,就需要跟外部交互,从而形成异步操作。由于异步操作实在太多,JavaScript 不得不提供很多异步语法。这就好比,有些人老是受打击, 他...

    五月君
  • MongoDB 多文档事务实践篇—教你如何在 Node.js 中应用

    MongoDB 在单文档操作中具有原子性,在多文档操作中就不再具有此特性,通常需要借助事务来实现 ACID 特性。

    五月君
  • Nodejs Stream pipe 的使用与实现原理分析

    通过流我们可以将一大块数据拆分为一小部分一点一点的流动起来,而无需一次性全部读入,在 Linux 下我们可以通过 | 符号实现,类似的在 Nodejs 的 St...

    五月君
  • 带你入坑01-weex-搭建环境

    安装 Node.js 环境成功后,npm 包管理工具也会自动安装成功 输入下面命令检查一下

    酷走天涯
  • 图片文字如何转换成Word,这个简单方法你得会

    图片文字如何转换成Word?这是很多人在工作中都会遇到的问题,当你看到一个很好看的图片上面有你喜欢的文字,想把上面的文字保存下来,但是如果一个一个把字打出来那就...

    高效办公
  • 【leetcode】 46. Permutations

    Given a collection of distinct numbers, return all possible permutations.

    py3study
  • 来自后方世界的隐匿威胁:后门与持久代理(一)

    干了十几年安全工作,发现一些同行只是把简单的工具扫描和渗透测试当成了全部工作,拿到需要的数据及测试结果既为完成工作。可各位兄弟,咱扪心自问,这样的安全测试能叫真...

    FB客服
  • 如何监控和诊断JVM堆内和堆外内存使用?

    可以使用综合性的图形化工具,如JConsole、 VisualVM(注意,从Oracle JDK 9开始, VisualVM已经不再包含在JDK安装包中)等。这...

    葆宁
  • 【机器学习】马尔科夫决策过程

    本文介绍了马尔可夫决策过程,首先给出了马尔可夫决策过程的定义形式,其核心是在时序上的各种状态下如何选择最优决策得到最大回报的决策序列,通过贝尔曼方程得到累积回报...

    yuquanle
  • 网络资源生命周期管理-设备篇

    "鹅厂网事"由深圳市腾讯计算机系统有限公司技术工程事业群网络平台部运营,我们希望与业界各位志同道合的伙伴交流切磋最新的网络、服务器行业动态信息,同时分享腾讯在网...

    鹅厂网事

扫码关注云+社区

领取腾讯云代金券