基于 Swoole 实现支持高并发的实时弹幕功能(上)
前面我们已经介绍完了 Swoole 的所有功能特性以及集成到 Laravel 框架的注意事项,接下来学院君带大家来做一些实践小项目练练手,在实践篇里,我们将完成两个项目,一个是简单的弹幕功能,一个是在线聊天室,首先我们来实现弹幕功能。
弹幕服务后端实现
随着哔哩哔哩之类的弹幕视频网站和在线直播视频的兴起,弹幕功能越来越常见,所谓弹幕其实就是视频界面上满天飞评论,就像下面这种:
这样一来,我们可以边看视频,边发评论,而且发布之后立即出现在弹幕里,能够被所有人看到,同时我们也能实时看到其他人发的弹幕,这就告别了传统评论要到页面底部去发布,发布后也不能实时被其他人看到的时代。
在出现 Ajax 之前甚至页面要刷新才能看到自己的评论,即使是 Ajax 出现后,也只是自己刚发的评论能立即看到,别人要看到我们的评论,或者我们想看其他人的最新评论,还是要刷新页面,所以弹幕这一功能极大提高了交流的实时性和效率。但背后也是因为有新技术的支撑,如果在 Ajax 时代,这是无法想象的,因为只能通过 Ajax 不断去轮询后台接口获取最新评论再渲染到页面,对性能和带宽都有负面影响,而且如果数据没有变更的话,这种请求也是浪费的,此外,对于一些直播平台动辄上万人同时在线发弹幕,对系统的并发性也有要求,Ajax 很难胜任这一工作。
那么弹幕功能的后台服务是怎么实现的呢?借助我们前面介绍的 WebSocket 协议,你已经很快能想到这一解决方案,没错,弹幕后台就是基于 WebSocket 通信实现的,用户在客户端发送弹幕,然后后端把请求转发到 WebSocket 服务器进行处理,最后再把这些消息通过长连接广播给连接到 WebSocket 服务器的客户端进行显示,这样一来无论是实时性还是并发性都得到了解决,在 PHP 中,我们可以基于 Swoole 来实现这个 WebSocket 服务器,关于这一点,我们前面已经介绍过:
在这个小项目中,我们依然基于 Laravel 的 Swoole 扩展包 LaravelS 在 Laravel 5.8 中实现一个 WebSocket 服务器,以便完成弹幕后端服务开发,在前端,我们将基于一个 Vue 弹幕组件 vue-baberrage 实现一个自定义的 Vue 组件用于完成弹幕页面的渲染以及与后端 WebSocket 服务器的通信。
前端 Vue 组件开发
接下来,我们来开始我们的开发工作,首先,我们在前端完成自定义 Vue 组件的开发。
安装 vue-baberrage 依赖
如果你的 Laravel 项目还没有初始化前端依赖的话,执行下面的命令初始化(项目根目录下执行):
npm install
然后安装 Vue 弹幕组件 vue-baberrage
:
npm install vue-baberrage --save
由于这个组件只能用于弹幕消息的渲染,所以安装完成后我们还要基于这个组件自定义一个自己的 Vue 组件实现弹幕的发送和最终显示,我们将自定义的 Vue 组件命名命名为 danmu-component
,并且在 resources/js/components
创建对应的源文件 DanmuComponent.vue
。
引入 vue-baberrage
接下来,在 resources/js/app.js
中引入 vue-baberrage
组件并使用,以便可以在 DanmuComponent.vue
中可以使用这个组件,并且在该文件中声明 danmu-component
组件:
window.Vue = require('vue');
import {vueBaberrage} from 'vue-baberrage';
Vue.use(vueBaberrage);
...
Vue.component('tasks-component', require('./components/TasksComponent.vue').default);
Vue.component('danmu-component', require('./components/DanmuComponent.vue').default);
编写 DanmuComponent.vue
接下来,我们编写 DanmuComponent.vue
组件的实现代码如下,其中包括了样式、模板和脚本代码:
<style lang="scss" scoped>
#danmu {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
.stage {
height: 300px;
width: 100%;
background: #025d63;
margin: 0;
position: relative;
overflow: hidden;
}
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.baberrage-stage {
z-index: 5;
}
.baberrage-stage .baberrage-item.normal{
color:#FFF;
}
.top{
border:1px solid #66aabb;
}
.danmu-control{
position: absolute;
margin: 0 auto;
width: 100%;
bottom: 300px;
top: 70%;
height: 69px;
box-sizing: border-box;
text-align: center;
display: flex;
justify-content: center;
div {
width: 300px;
background: rgba(0, 0, 0, 0.6);
padding: 15px;
border-radius: 5px;
border: 2px solid #8ad9ff;
}
input,button,select{
height:35px;
padding:0;
float:left;
background:#027fbb;
border:1px solid #CCC;
color:#FFF;
border-radius:0;
width:18%;
box-sizing: border-box;
}
select{
height:33px;
margin-top:1px;
border: 0px;
outline: 1px solid rgb(204,204,204);
}
input{
width:64%;
height:35px;
background:rgba(0,0,0,.7);
border:1px solid #8ad9ff;
padding-left:5px;
color:#FFF;
}
}
</style>
<template>
<div id="danmu">
<div class="stage">
<vue-baberrage
:isShow = "barrageIsShow"
:barrageList = "barrageList"
:loop = "barrageLoop"
:maxWordCount = "60"
>
</vue-baberrage>
</div>
<div class="danmu-control">
<div>
<select v-model="position">
<option value="top">从上</option>
<option value="abc">从右</option>
</select>
<input type="text" style="float:left" v-model="msg"/>
<button type="button" style="float:left" @click="addToList">发送</button>
</div>
</div>
</div>
</template>
<script>
import { MESSAGE_TYPE } from 'vue-baberrage'
export default {
name: 'danmu',
data () {
return {
msg: '你好,学院君!',
position: 'top',
barrageIsShow: true,
currentId: 0,
barrageLoop: false,
barrageList: []
}
},
methods: {
removeList () {
this.barrageList = []
},
addToList () {
if (this.position === 'top') {
this.barrageList.push({
id: ++this.currentId,
avatar: 'https://laravel.geekai.co/assets/avatars/numxwdxf8lrtrsol.jpg',
msg: this.msg + this.currentId,
barrageStyle: 'top',
time: 8,
type: MESSAGE_TYPE.FROM_TOP,
position: 'top'
})
} else {
this.barrageList.push({
id: ++this.currentId,
avatar: 'https://laravel.geekai.co/assets/avatars/numxwdxf8lrtrsol.jpg',
msg: this.msg,
time: 15,
type: MESSAGE_TYPE.NORMAL
})
}
}
}
}
</script>
在这里,我们设置了初始化的弹幕信息,以及弹幕的显示和发送模块,弹幕的渲染借助 vue-baberrage
组件完成,而弹幕的发送和与 WebSocket 服务器的交互需要我们自己实现。这里,我先定义发送事件处理函数的实现逻辑为推送弹幕消息到 barrageList
数组,然后 vue-baberrage
组件会根据该数组的变化动态渲染弹幕消息。这里面涉及到的 vue-baberrage
组件配置信息可以参考其官方文档,isShow
用于表示是否显示该组件,barrageList
用于定义弹幕数组用于显示,loop
表示是否循环显示弹幕,我们可以在弹幕数组的消息对象中设置头像、id、消息文本、显示时间和类型等字段,其中,显示类型分为两种,一种是显示在组件顶部(对应 MESSAGE_TYPE.FROM_TOP
),一种是从右到左正常显示(对应 MESSAGE_TYPE.NORMAL
),这些配置都很简单,通过名称基本上就可以知道是用来干什么的了。
现在,我们只实现纯前端交互和渲染,与 WebSocket 服务器的通信及返回消息处理我们放到下一篇后端功能完成之后再来实现。
在视图中引入 danmu-component
接下来,我们在 resources/views
目录下创建一个 Blade 视图模板 danmu.blade.php
,并初始化代码如下:
@extends('layouts.app')
@section('content')
<danmu-component></danmu-component>
@endsection
该视图继承自 layouts.app
布局(如果没有的话通过 php artisan make:auth
生成),然后主体非常简单,就是引入上一步编写的 danmu-component
组件,所有的细节都封装到了这个 Vue 组件中。
注册演示路由
最后,到路由文件 routes/web.php
中注册一个用于演示弹幕的路由 /danmu
:
Route::get('/danmu', function() {
return view('danmu');
});
访问弹幕演示页面
至此,所有前端编码工作就完成了,在项目根目录下通过如下命令编译前端资源:
npm run dev
然后在浏览器中访问弹幕演示路由 http://laravel58.test/danmu
,在输入框输入数据点击「发送」按钮就可以看到弹幕渲染的效果了:
下一篇我们将完成后端 WebSocket 服务器的编写以及前端与后端的交互,从而实现真正意义上的弹幕功能。
20 条评论
绑定域名了没有 通过 127.0.0.1 访问也会有这个问题
已经绑定 是通过域名访问的
组件不显示时,注意
Vue.component()
要在'const app = new Vue({});'之前。Vue.component('tasks-component', require('./components/TasksComponent.vue').default); 你没有写这个组件,还是说本来就没有?
没有用到就不需要 可能是前面的示例项目遗留下来的
npm run development时候报错 ,请问这个问题怎么解决?
sh: 1: cross-env: not found npm ERR! code ELIFECYCLE npm ERR! syscall spawn npm ERR! file sh npm ERR! errno ENOENT npm ERR! @ development:
cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js
npm ERR! spawn ENOENT npm ERR! npm ERR! Failed at the @ development script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm WARN Local package.json exists, but node_modules missing, did you mean to install?npm ERR! A complete log of this run can be found in: npm ERR! /root/.npm/_logs/2020-02-23T16_21_20_998Z-debug.log npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! @ dev:
npm run development
npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the @ dev script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm WARN Local package.json exists, but node_modules missing, did you mean to install?npm ERR! A complete log of this run can be found in: npm ERR! /root/.npm/_logs/2020-02-23T16_21_21_013Z-debug.log
npm install cross-env --save
请问下学院君,不new vue(),可以直接使用吗?还有能不能把源码贴出来啊
搞了两天终于解决不页面不显示的问题了。我遇到的是怎么弄页面都不显示js代码,我把编译好的app.js打开和服务器的对比发现,app.js竟然没有更新,原来是我按照这个教程写的nginx 配置文件导致每次编译文件名一样就会存在缓存,这我原来的配置文件是自己写的不存在,这次项目是copy的导致一些细节没有配置!反正就是坑吧
php artisan make:auth 是什么