- 非实时弹幕
- 多节点实时弹幕
添加弹幕
js
add: async (req) => {
let { content, episodeId, playTime, productId } = req.body
if (!(content && episodeId && Number(playTime) >= 0 && productId)) {
return BackCode.buildError({ msg: '缺少必要参数' })
}
let token = req.headers.authorization.split(' ').pop()
let userInfo = SecretTool.jwtVerify(token)
let barrageItem = {
episode_id: episodeId,
product_id: productId,
play_time: playTime,
content: content,
account_id: userInfo.id,
head_img: userInfo.head_img,
username: userInfo.username,
del: 0,
}
await DB.BulletScreen.create(barrageItem)
return BackCode.buildSuccess()
},
具体解释如下:
let { content, episodeId, playTime, productId } = req.body;
: 这行代码从请求的body
中解构出弹幕的内容 (content
)、集数 ID (episodeId
)、播放时间 (playTime
) 和产品 ID (productId
)。这些参数都是客户端传来的。if (!(content && episodeId && Number(playTime) >= 0 && productId)) { return BackCode.buildError({ msg: '缺少必要参数' }) }
: 这行代码用于判断是否缺少必要参数。如果其中任何一个参数为空或播放时间小于零,就会返回一个错误响应,提示缺少必要参数。let token = req.headers.authorization.split(' ').pop();
: 这行代码从请求头中获取授权令牌 (authorization
),然后将其拆分为两部分,取第二部分作为 token。let userInfo = SecretTool.jwtVerify(token);
: 这行代码使用SecretTool
工具类的jwtVerify
方法对 token 进行解析,并返回包含用户信息的对象。let barrageItem = { ... }
: 这是一个对象字面量,用于存储要添加到数据库的弹幕数据。其中包含集数 ID、产品 ID、播放时间、内容、账户 ID、头像、用户名和删除标志等信息。await DB.BulletScreen.create(barrageItem);
: 这行代码使用模型BulletScreen
的create
方法创建一个新的弹幕实例,并将前面构建的barrageItem
对象作为参数传递给它。通过这个操作,弹幕数据被写入到数据库中。return BackCode.buildSuccess();
: 最后,返回一个成功的响应。
弹幕查询
js
list_by_episode: async (req) => {
let { beginTime, endTime, episodeId, productId } = req.body
if (!(productId && episodeId && Number(beginTime) >= 0 && endTime)) {
return BackCode.buildError({ msg: '缺少必要参数' })
}
let barrageList = await DB.BulletScreen.findAll({
where: { play_time: { [Op.between]: [beginTime, endTime] }, episode_id: episodeId, product_id: productId }
})
return BackCode.buildSuccessAndData({ data: barrageList })
},
现阶段浏览器网页主要的请求方式
用户如何在浏览器上不作任何操作的情况下接收到服务器的消息?
定时短轮询
定时1-3秒请求服务器接口查询状态
场景:扫码登录、扫码支付等等
缺点:
1.虽然是用户无感知得从服务器获取消息,但是控制台上会有一堆的网络请求; 2.而且定时频繁地请求服务器接口,会消耗带宽,增加服务器的负担; 3.用户扫码后需要等待几秒时间,页面才能跳转,会有明显的卡顿;
长轮询
将http接口请求的超时时间设置大些(满足用户扫码所需的时间),如果超时就发起下一次请求
场景:百度云网盘app扫码登录等
特点:
1.用户扫码之后,在电脑端网页就秒跳转,用户不需要等待
以上两种方式称为服务器推送技术本质上还是客户端主动请求数据,只不过在用户无感知的情况下,也叫comet技术
为什么使用websocket?
像轮询的机制还是以客户端发起的请求,对于简单的扫码登录状态查询可以适用,但是遇到数据传输量大的场景就不行了,同时不能保证及时接受到消息,类似视频会议、网页游戏等业务场景,需要两端互相传输大量的数据,因此就需要使用websocket
- 由来
现在使用最广泛的应用层协议HTTP1.1基于TCP协议,同一时间内客户端和服务端只能有一方主动发送数据,称为半双工
而TCP协议是全双工的,同一时间内两端都可以同时像对方发送数据,称为全双工
为了满足两端相互发送大量的场景,兴起了基于TCP协议的应用层协议websocket
-
什么websocket?
- 定义
是一种在单个TCP连接上进行全双工通信的协议使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
markdown
- 优点
- 节省资源:对比客户端轮询请求后端接口,更加节省服务器资源和宽带
- 更强实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据,延迟更小
- 保持连接状态:HTTP请求建立一次链接就断开,而websocket是持久性链接
- 使用场景
- 弹幕
- 多玩家游戏
- 视频会议
- 股票基金报价实时更新
- 体育实况更新
- 音视频聊天
- 在线教育
- 社交订阅
弹幕获取-前端模块编码实战开发
自定义封装websocket
js
/**
* 弹幕逻辑配置
*/
import { useRef, useState } from "react";
import { io } from "socket.io-client";
const useSocket = () => {
// 弹幕实例变量名
const socket = useRef<any>(null);
// 发送的弹幕
const videoDanmuList = useRef<any[]>([]);
const initialize = () => {
// 建立传输链接 http://127.0.0.1:8081
socket.current = io("ws://127.0.0.1:8081");
socket.current.on("connect", () => {
console.log("socketio已连接");
});
onBulletChat();
};
// 发送弹幕事件
const handleAddDanmu = (data: any) => {
socket.current.emit("bulletChat", data);
};
// 监听bulletChat事件
const onBulletChat = () => {
socket.current.on("message", (data: any) => {
videoDanmuList.current.push(data);
});
};
return {
videoDanmuList,
initialize,
handleAddDanmu,
onBulletChat,
};
};
export default useSocket;
后段启动websocket
app.js
js
const express = require('express');
const app = express();
const { createServer } = require('http');
const server = createServer(app)
server.listen(8081, () => {
console.log('服务启动在:http://127.0.0.1:8081')
})
websocket.js
js
const { Server } = require('socket.io')
const Redis = require("ioredis");
// redis发布的api
const clientPublish = new Redis({ port: 6379, host: '8.130.120.189', password: 'xdclass.net' });
// redis订阅api
const clientSubscribe = new Redis({ port: 6379, host: '8.130.120.189', password: 'xdclass.net' });
clientSubscribe.subscribe('chat');
const websocket = (server) => {
// 实例化socket
const io = new Server(server, { cors: { origin: '*' } })
// websocket建立链接
io.on('connection', (socket) => {
console.log('有客户端链接进来了')
// 监听bulletChat事件
socket.on('bulletChat', (info) => {
clientPublish.publish('chat', JSON.stringify(info))
})
})
// 订阅redis收到消息后,执行websocket的消息推送客户端
clientSubscribe.on('message', (channel, message) => {
io.emit('message', JSON.parse(message))
})
}
module.exports = websocket