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

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

效果预览

先看下,咱们实现的最终效果,以下所示:git

你也能够在浏览器分别输入如下两个 URL 地址进行体验:github

技术选型

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

前端实现

HTML 页面布局

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

  • 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() 这也是核心,经过这两个函数能够轻松的实现客户端与服务端的双向通讯。docker

  • 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 发送到服务端,由服务端转接到另外一客户端。express

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

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
复制代码

代码示例:

Demo 在线体验:

总结

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

相关文章
相关标签/搜索