赞
踩
在前端中 WebSocket 是H5新增的对象
主要作用有:实时通讯 长连接 双向传输 后端主动推送数据
websocket实例的主要事件
前端:
直接new 一个实例
const ws = new WebSocket("ws://localhost:9100");
大部分浏览器已经支持 WebSocket 对象 协议格式为ws(不是 file http)
主要事件:
open | 建立链接 |
close | 断开链接 |
error | 发生错误 |
message | 发送消息给后端 |
后端:
使用node 来简单搞一个 本地服务、后端要依赖第三方包使用websocket 这里以ws为例
npm i ws@8.10.0
引入ws 创建服务 配置端口和前端一致
const ws = require('ws')
const wss= new ws.Server({port:9100})
主要事件:
open | 建立链接 |
close | 断开链接 |
error | 发生错误 |
connection message | 有客户端连接上 接收到客户端发送来的消息 一般 message事件在 connection里面 |
样式部分省略...
使用双向绑定获取 用户输入的昵称和 选择的头像 头像v-for渲染数据 点击时currentIndex修改为当前的索引 根据索引 增加高亮边框和 选择此数据传给下一步
- <input type="text" placeholder="请输入发言昵称" v-model="nickname" id="input" />
- <ul class="avatar">
- <li v-for="(aa,index) in avatar_list"
- :key="index"
- :class="{curr : currentIndex===index}"
- @click="bianse(index)">
- <img :src="aa" alt="">
- </li>
验证输入用户的长度要在1-9位
想服务器发起请求 存放昵称(禁止其他人使用)
收集数据保存到 localStorage 中 进行下一步
- methods:{
- defind(){
- if (this.nickname.length < 1) {
- return alert("请输入昵称");
- }
- if (this.nickname.length > 9) {
- return alert("输入昵称过长");
- }
- this.$http.get(`/login/${this.nickname}`).then(res => {
- if(res.data.status === 1){
- localStorage.setItem("nickname", this.nickname);
- localStorage.setItem("avatar", this.avatar_list[this.currentIndex]);
- this.$router.push('/about')
- }else{
- return alert("昵称已被占用");
- }
- })
- },
- bianse(index){
- this.currentIndex = index
- }
- }
预留组件中需要的数据 进入组件和挂载元素 生命周期中进行对应的操作
进入到页面 简易判断 前一步保存到 localStorage 的数据是否存在 如果不存在自动返回登录页面重新设置 防止用户跳过登录直接进入到 聊天室 没有昵称导致的一系列错误 这一步也可以使用 导航守卫来实现
- data(){
- return {
- nickname:'', // 用户的昵称
- message:'', // 用户发送的消息
- record:[], // 消息记录数组
- ws:null, // ws 实例 预留变量
- user_list:[] // 实时在线人数列表
- }
- },
- created() {
- this.nickname = localStorage.getItem("nickname")
- if (!this.nickname) {
- return this.$router.push('/');
- }
- },
mounted 生命周期中 创建 WebSocket实例
添加 ws相关事件 这里只需要
open 连接上ws服务器端了 发送欢迎消息
message 接收到服务器返回来的数据 渲染到页面 聊天信息部分 并判断是新进入的用户发送的消息 还是老用户发送的消息 如果是新用户 就添加到左侧在线列表 老用户此步骤忽略
业务流程:连接上后端发送欢迎消息 =》后端接收消息 返回给每个客户端 =》 客户端接收到服务端发来的消息 渲染到 消息列表 并且根据条件 渲染左侧在线列表
- mounted() {
- this.ws = new WebSocket("ws://localhost:9100");
- this.ws.addEventListener("open", () => {
- this.ws.send(
- JSON.stringify({
- user: this.nickname,
- avatar:localStorage.getItem('avatar'),
- dateTime: this.nowTimeFormatChinese(new Date()),
- message:'欢迎 ' +this.nickname + ' 来到聊天室',
- })
- );
- });
- this.ws.addEventListener("message", (e) => {
- const data = JSON.parse(e.data)
- this.record.push(data);
- const flag = this.user_list.filter(x => x.user === data.user)
- if(flag.length === 0){
- this.user_list.push(data);
- }
- });
- },
点击发送按钮 组织好数据 ws.send 发送给服务端
发送消息 返回渲染之后 滚动跳滚到最新消息处
this.$refs.lists.scrollTop += 100
// window.scrollTo(0, document.body.scrollHeight);
还有格式化时间的方法
- methods:{
- send(){
- if (!this.message.trim().length) {
- return alert("请输入内容");
- }
- this.ws.send(
- JSON.stringify({
- user: this.nickname,
- avatar:localStorage.getItem('avatar'),
- dateTime: this.nowTimeFormatChinese(new Date()),
- message: this.message,
- })
- );
- this.message = "";
- setTimeout(()=>{
- this.$refs.lists.scrollTop += 100
- // window.scrollTo(0, document.body.scrollHeight);
- },100)
- },
- padZero(n){
- return n > 9 ? n : "0" + n;
- },
- nowTimeFormatChinese(riqi){
- let hour = this.padZero(riqi.getHours()),
- min = this.padZero(riqi.getMinutes()),
- sec = this.padZero(riqi.getSeconds())
- return hour + "时" + min + "分" + sec + "杪";
- }
- },
离开页面(销毁组件)时 清楚自己的昵称 ---左侧的在线列表 和 服务器端的命名空间
- deactivated(){
- const index = this.user_list.findIndex(x => x.user === this.nickname)
- this.user_list.splice(index, 1)
- this.$http.get(`/loginout/${this.nickname}`)
- },
渲染消息列表时 判断是不是自己所发的消息 返回来的user === 自己的nickname
是的话添加 meSay 样式 右侧显示 作为区分
- <template>
- <div class="about">
- <ul id="list" ref="lists">
- <li v-for="(n,index) in record" :key="index" :class="{meSay : n.user === nickname}">
- <div>
- <div class="cow">
- <img :src="n.avatar" alt="" class="avatar">
- <p class="ppp">
- <span>{{n.user}}</span>
- <br>
- <span>{{n.dateTime}}</span>
- </p>
- </div>
-
- <div class="nei">
- {{n.message}}
- </div>
- </div>
-
-
- </li>
- </ul>
- <div class="bottom">
- <div class="people">
- <h3>当前在线人数:{{this.user_list.length}}</h3>
- <div class="user_list" v-for="n in user_list" :key="n.user">
- <img :src="n.avatar" alt="">
- {{n.user}}
- </div>
- </div>
- <textarea id="message" v-model="message" @keyup.enter="send"></textarea>
- <button id="send" @click="send">发送</button>
- </div>
- </div>
- </template>
ws部分比较简单 监听链接 和 接收消息的事件 出发了就遍历所有链接的客户端 把数据原封不动的发送出去
- const ws = require('ws')
-
- const wss= new ws.Server({port:9100})
-
- wss.on('connection',(client)=>{ // clent 这个客户端链接了
- client.on('message',(msg)=>{ // 并且发来了数据
- const radio = msg.toString() // 数据转换格式防止乱码
- wss.clients.forEach(e =>{ // 遍历再原封不动发送给每个链接的客户端
- e.send(radio)
- })
- })
- })
管理昵称命名空间的端口
引入express 快速搭建本地服务器
npm i express@4
使用中间件 解决跨域问题
创建 activeUser 数组储存 已在线的用户昵称、登录时发送请求携带 nickname req.params 获取路径中的形参 判断在 activeUser 中是否存在 如果存在添加失败 不存在 添加进去 这样保证昵称不重复、 注销时发送请求 携带nickname 在activeUser 查找到 并且 删除它
- // 导入 express 模块
- const express = require('express')
- // 创建 express 的服务器实例
- const app = express()
-
-
- // 这样也可以解决跨域问题
- app.use(function(req,res,next){
- // 第二个 * 代表通配符 也可以指定具体的网站 http://www.wsg3096.com
- const contentType = 'application/json; charset=utf-8'
- res.setHeader('Content-Type',contentType)
- res.setHeader('Access-Control-Allow-Origin','*')
- // 后面的也可以用通配符
- res.setHeader('Access-Control-Allow-Methods','OPTIONS,GET,PUT,POST,DELETE')
- // 设置其他的请求头
- res.setHeader('Access-Control-Allow-Headers','Content-Type','X-Custom-Header')
- next()
- })
-
-
- const activeUser = []
-
- // 登录的 API 接口
- app.get('/api/login/:nickname', (req, res) => {
- const nickname = req.params.nickname
- const find = activeUser.find(x => x=== nickname)
- if(find){
- return res.send({
- status:0,
- msg:'用户昵称已经被占用'
- })
- }else{
- activeUser.push(nickname)
- return res.send({
- activeUser,
- status:1,
- msg:'成功进入聊天室队列'
- })
- }
- })
-
- // 注销的接口
- app.get('/api/loginout/:nickname', (req, res) => {
- const nickname = req.params.nickname
- const index = activeUser.findIndex(x => x=== nickname)
- activeUser.splice(index,1)
- return res.send({
- activeUser,
- status:200,
- msg: `成功释放${nickname}的命名空间`
- })
- })
-
- // 调用 app.listen 方法,指定端口号并启动web服务器
- app.listen(7777, function () {
- console.log('Express server running at http://127.0.0.1:7777')
- })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。