IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    实战WebSocket聊天室:从开发到部署上线

    zuley发表于 2022-04-03 14:43:16
    love 0

    本文仅对一些关键性的知识点进行解释,具体请Fork源码学习。

    Demo页面如果没啥人的话可以自己新建几个页面复制地址进入,每个页面都是一个独立的访客,兼容PC和移动端访问。

    Demo 演示

    Github源码

    博客原文

    前端实现

    前端技术栈

    • Parcel:构建工具,零配置的打包构建工具
    • socket.io:跨平台的WebSocket通信库,具有前后端一致的API,可以触发和响应自定义的事件。

    前端界面

    界面布局这个没有啥说的,直接按自己喜好写就行了。也可以直接参照本项目DEMO和源码。

    socket.io 简介

    socket.io 有前后端一致的API,所以在前端和Node端使用区别不大。

    socket.io 最主要的API是触发和响应自定义的事件,除了connect,message,disconnect这些事件的名字不能使用之外,你可以触发任何自定义的事件名称。下面列出本项目中所使用到的一些自定义事件。

    socket.io 官网

    # 客户端触发自定义事件:test
    socket.emit('test', data)
    
    # 服务端响应事件
    socket.on('test', data => {})
    复制代码

    前端触发事件

    • 登录聊天室
    • 发送消息

    前端响应事件

    • 登录状态
    • 系统通知
    • 消息发送

    源码解析

    连接服务器

    import io from './assets/js/socket.io'
    let socket = io('ws://47.91.235.153:3000')
    // 连接服务器
    socket.on('connect', function () {
      console.log('成功连接服务器')
    })
    复制代码

    登录聊天室

    进入页面弹窗要求输入用户名,后端验证用户名不重复后即可关闭登录弹窗进入聊天室。

    # 发送登录事件
    function userLogin () {
      let loginName = document.getElementById('js-loginName').value
      if (loginName === '') {
        alert('你必须输入用户名')
      } else {
        // 发送登录事件
        socket.emit('login', {
          name: loginName
        })
      }
    }
    oLoginBtn.addEventListener('click', userLogin)
    
    # 响应登录状态
    socket.on('login', function (data) {
      if (data.status === 'ok') {
        loginStatus = true
        oLogin.style.visibility = 'hidden'
      } else {
        alert(data.text)
      }
    })
    复制代码

    系统通知

    系统通知只能由服务端发送,主要返回用户进入、离开房间的通知,并返回当前在线用户。

    socket.on('sys', function (data) {
      // 在线人数
      oCount.innerHTML = data.count
      // 加入消息列表
      oMessageBox.innerHTML += `<li class="sys">
        <div class="name">系统通知</div>
        <div class="message">${data.text}</div>
      </li>`
    	// 遍历显示在线用户
      let sUser = ''
      data.users.forEach(el => {
        sUser += `<li>${el}</li>`
      });
      oUserBox.innerHTML = sUser
    })
    复制代码

    消息发送

    function sendMessage () {
      // 获取输入框
      let oText = document.getElementById('js-text')
      // 当前输入的内容
      let sText = oText.value
      // 为空不提交
      if (sText === '') {
        return false
      }
      // 触发消息发送事件
      socket.emit('message', {
        name: nickName,
        text: sText
      })
      // 消息列表追加本人发送的消息
      oMessageBox.innerHTML += `<li class="my">
        <div class="name">${nickName}</div>
        <div class="message">${sText}</div>
      </li>`
      // 重置内容为空
      oText.value = ''
      // 消息列表滚动到最底部
      oMessageBox.scrollTop = oMessageBox.scrollHeight
    }
    oEnter.addEventListener('click', sendMessage)
    复制代码

    接收群聊消息

    接收后台发送的广播消息,不包含本人发送的消息。

    socket.on('message', function (data) {
      // 消息列表追加消息
      oMessageBox.innerHTML += `<li>
        <div class="name">${data.name}</div>
        <div class="message">${data.text}</div>
      </li>`
      // 消息列表滚动到底部
      oMessageBox.scrollTop = oMessageBox.scrollHeight
    })
    复制代码

    使用 Parcel 开发和打包

    Parcel的使用非常简单,不需要任何配置即可运行和打包应用程序

    Parcel 中文官网

    # 安装
    npm install -g parcel-bundler
    
    # 开发:http://localhost:1234/ 访问
    parcel index.html
    
    # 编译
    parcel build index.html
    复制代码

    Node 服务端实现

    Node 技术栈

    • http
    • socket.io

    Node端 socket.io

    拥有和前端一样的API,这里不多做解释

    io.on('connection', function (socket) {
    	// 响应当前连接用户的事件
    	socket.on('test', data => {})
    	// 给当前连接的用户发送事件
    	socket.emit('test', data)
    	// 广播给所有人
    	io.emit('test', data)
    	// 广播给除当前用户外所有人
    	socket.broadcast.emit('test', data)
    })
    复制代码

    源码解析

    启用WebSocket

    var app = require('http').createServer()
    var io = require('socket.io')(app)
    // WebSocket 连接
    io.on('connection', function (socket) {
    	// 所有的事件触发响应都写在这里
    })
    // 启用3000端口
    app.listen(3000, function () {
      console.log('WebSocket 启用端口 on *: 3000')
    })
    复制代码

    用户登录聊天室

    因为只是个练习小项目,也没有真正的用户中心啥的。只用了一个数组users来存储当前在线用户。

    socket.on('login', function (data) {
    	// 检查 users 中是否有重名用户
    	if (users.indexOf(data.name) >= 0) {
    		console.log(data.name + ' 已有重名用户,请重新输入昵称。')
    		// 发送登录失败事件
    		socket.emit('login', {
    	   		status: 'err',
    	    	text: '已有重名用户,请重新输入昵称。'
    		})
    	} else {
    		// 添加一个用户
    		users.push(data.name)
    		// 设置当前用户的 nickName
    		socket.nickName = data.name
    		console.log(data.name + ' 进入了房间')
    		console.log('当前用户', users)
    		// 发送进入房间的系统通知
    		io.emit('sys', {
    			text: socket.nickName + ' 进入了房间',
    			count: users.length,
    			users: users
    		})
    		// 发送登录成功的通知
    		socket.emit('login', {
    			status: 'ok'
    		})
    	}
    })
    复制代码

    消息推送

    接收用户发送的信息后广播给除发送用户外的所有人

    socket.on('message', function (data) {
    	socket.broadcast.emit('message', data)
    })
    复制代码

    用户断开连接

    socket.on('disconnect', function () {
    	let index = users.indexOf(socket.nickName)
    	if (index >= 0) users.splice(index, 1)
    	// 用户离开房间发送系统通知
    	io.emit('sys', {
    		text: socket.nickName + ' 离开了房间',
    		count: users.length,
    		users, users
    	})
    	console.log(socket.nickName + '离开了房间')
    	console.log('当前用户', users)
    })
    复制代码

    部署上线

    项目虽小却也是前后端分离的,所以作为练习将项目的代码分开部署到了不同的服务器。

    NodeJS 服务端部署

    后端代码是部署到了阿里云的香港服务器的,系统 CentOS 7。使用 Docker 运行了一个node环境的容器。

    docker 中文手册

    阿里云的服务器安全性很高,但也因此有超多的坑,主要注意以下几点。

    • 服务器默认是关闭所有对外端口的,需要在控制台中添加对应的安全组策略。
    • 本地ssh和FTP操作频繁可能被拉入黑名单,需要在云盾·DDoS高防IP中设置本地IP到白名单中
    • CentOS 7 默认防火墙从iptables换成了 FirewallD

    1、在服务器中安装好 ftp 和 Docker,安装方法谷歌有很多 2、使用 Docker 安装 Node 镜像

    $ Docker pull node
    复制代码

    3、运行容器并挂载本地目录(容器中的所有数据都是缓存,所以对一些需要经常变动修改的文件直接挂载到本地目录)

    $ docker run -it -p 3000:3000 \
    $ --mount type=bind,source=/home/www/chat,target=/home/www/chat \
    $ node:latest \
    $ /bin/bash
    复制代码

    4、上传代码并进入容器运行

    将代码文件上传到服务器 /home/www/chat中,然后进入容器。

    • index.js
    • package.json
    $ npm install
    $ node index.js
    复制代码

    如果从容器退出了需要重新进入容器

    # 容器ID可以用 docker ps -a 查看
    $ docker exec -it f9dd88d7f
    # 进入容器中的项目目录
    $ cd /home/www/chat
    # 安装依赖
    $ npm install
    # 运行项目
    $ node index.js
    复制代码

    Web 前端部署

    前端的代码部署相对后端来说简单很多,因为前端代码只有一些HTML、CSS和JS等静态的文件,随便找个静态服务器放就可以。

    对于此类项目这里强烈推荐使用阿里云的对象存储OSS,超级便宜基础版一年只需要9块钱。

    1. 新建 Bucket 存储块
    2. 在域名管理中绑定域名
    3. 在基础设置中设置静态网站托管
    4. 打包上传编译后的前端文件

    打包前端代码

    $ parcel build index.html
    复制代码

    打包成功后将/dist目录上传到刚刚在阿里云中建立的OSS存储块中,修改index.html目录中的资源引用为根目录,并将index.html移动到根目录,然后通过绑定的域名访问即可



沪ICP备19023445号-2号
友情链接