由于博主是个忠实的英雄联盟粉丝,所以经常观看一些明星大神的直播。而一谈到直播,肯定会看到满屏幕飘来飘去的弹幕。那么问题来了,这些视频弹幕网站如何做到实时同步的?PHP如何开发一个类似的网站?

首先要搞定的是前端页面,最起码得有个框,让弹幕飞起来吧。一想到前台,博主头就大(毕竟我不喜欢去扣前端代码,而且做出来的东西还巨丑)。那咱们就百度一下吧,看看有什么好用的弹幕插件,现在开源的东西那么多。

经过搜索,找到了一个jQuery.danmu.js的开源项目。看了一下star的人还挺多。https://github.com/chiruom/jquery.danmu.js

于是乎,管他三七二十一,先down下来再说。

git clone https://github.com/chiruom/jquery.danmu.js.git
  • 1

大致一看目录结构如下:

进入demo目录,先运行一下例子看看结果呗。

果然,点开以后出现了一个高大上的页面,略看一下功能还挺多。但是问题来了,为啥我点击开始,一点反应也木有呢。

寻找原因ing。

原来是源文件中的jQuery插件的问题。在src目录下,并没有该文件

  <script src="../src/jquery-2.1.4.min.js"></script>
  • 1

算了还是调用百度的在线jQuery插件吧

  <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
  • 1

再一刷新,不出预料,成功运行。

很有意思,有木有,很激动有木有。然而重点才刚刚开始。

后端,那就先来说说弹幕的原理吧。弹幕,就相当于一个公共聊天室,都是一个客户端发送消息给服务端,服务端再将收到的消息广播给其他的客户端。

用传统的ajax轮询吗?不行,这样效率太低,想想各大火爆的直播平台都是同一时间几万人在线,几千人同时发弹幕,如果靠ajax轮询一个PHP接口的话服务器会吃不消的。且弹幕消息存储方案略显复杂,有人问为什么要存储呢?因为ajax使用的HTTP协议是无状态协议,A客户端和B客户端之间对于服务器来说没有任何标志,如果服务器要确保A客户端和B客户端分别在两次请求的时候服务器只返回这两个客户端没有获取过的弹幕消息,那么服务器端就必须使用一个缓存来标识某某客户端看过哪条弹幕消息。综上所述ajax可以实现小规模的弹幕通信方案,但是很麻烦。

好在最新的HTML5中加入了WebSocket协议,我们可以通WebSocket这种基于HTTP协议之上的即时通信协议来替代ajax这种传统的我问你答的老旧通信模式。而我们是PHPer,对于我们这种只懂PHP的人该如何编写WebSocket服务端呢?好在我们又得知PHP有一个Swoole扩展,我们在PHP语言中使用它可以很方便的构建一个WebSocket服务端。

关于Swoole,下面这段是其官网上的话:

PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。
Swoole可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。 使用PHP+Swoole作为网络通信框架,可以使企业IT研发团队的效率大大提升,更加专注于开发创新产品。

跟详细的东西请自行参考官网文档。这里就不在废话了。

http://wiki.swoole.com/wiki/page/479.html

还有一个问题需要解决,那就是,这个jquery.danmu.js是基于弹幕运行时间的一个插件。那又要如何做到实时呢。开始博主想的是在服务器端规定一个时间(即其连接时间),当有客户端连接时,返回服务器的当前时间戳,然后以此为依据开始计时。但是遇到的问题如下:

  • 该弹幕插件是按十分之秒计时制度。
  • 各浏览器上js的定时器的运行时间略有差异。
  • 时间不能完全同步。

好吧,博主走弯路子了(没做过这方面的东西,缺乏经验)。这个时候,就需要转变一种思路了。

websocket是实时通信的,哎,那所有客户端的时间,不一致就不一致吧,弹幕发的时间根据各个客户端的为准呗,都以当前各个客户端的时间来发,websocket只传递不包含时间的数据(好吧有点绕,我自己都感觉说饶了),咱们直接来上代码吧。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>弹幕made by diligentyang</title>
<style>
body {
font-family: "Microsoft YaHei" ! important;
font-color:#222;
}
pre {
line-height: 2em;
font-family: "Microsoft YaHei" ! important;
}
h4 {
line-height: 2em;
}
#danmuarea {
position: relative;
background: #222;
width:800px;
height: 445px;
margin-left: auto;
margin-right: auto;
}
.center {
text-align: center;
}
.ctr {
font-size: 1em;
line-height: 2em;
}
</style>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script src="../dist/jquery.danmu.min.js"></script>
</head> <body class="center">
Demo<br><br>
<!--黑背景和弹幕区-->
<div id="danmuarea">
<div id="danmu" >
</div>
</div>
<!--控制区-->
<div class="ctr" >
<button type="button" onclick="pauser()">弹幕暂停</button> &nbsp;&nbsp;&nbsp;&nbsp;
<button type="button" onclick="resumer() ">弹幕继续</button>&nbsp;&nbsp;&nbsp;&nbsp;
显示弹幕:<input type='checkbox' checked='checked' id='ishide' value='is' onchange='changehide()'> &nbsp;&nbsp;&nbsp;&nbsp;
弹幕透明度:
<input type="range" name="op" id="op" onchange="op()" value="100"> <br>
当前弹幕运行时间(秒):<span id="time"></span>&nbsp;&nbsp;
<!--设置当前弹幕时间(秒): <input type="text" id="set_time" max=20 />
<button type="button" onclick="settime()">设置</button>-->
<br>
发弹幕:
<select name="color" id="color" >
<option value="white">白色</option>
<option value="red">红色</option>
<option value="green">绿色</option>
<option value="blue">蓝色</option>
<option value="yellow">黄色</option>
</select>
<select name="size" id="text_size" >
<option value="1">大文字</option>
<option value="0">小文字</option>
</select>
<select name="position" id="position" >
<option value="0">滚动</option>
<option value="1">顶端</option>
<option value="2">底端</option>
</select>
<input type="textarea" id="text" max=300 />
<button type="button" onclick="send()">发送</button>
</div>
<script>
//WebSocket
var wsServer = 'ws://123.206.61.229:9505';
var websocket= new WebSocket(wsServer); websocket.onopen = function (evt) {
console.log("Connected to WebSocket server.");
/*websocket.send("gaga");*/
//连上之后就打开弹幕
$('#danmu').danmu('danmuResume');
}; websocket.onclose = function (evt) {
console.log("Disconnected");
}; websocket.onmessage = function (evt) {
console.log('Retrieved data from server: ' + evt.data);
var time = $('#danmu').data("nowTime")+1;
var text_obj= evt.data +',"time":'+time+'}';//获取加上当前时间
console.log(text_obj);
var new_obj=eval('('+text_obj+')');
$('#danmu').danmu("addDanmu",new_obj);//添加弹幕
}; websocket.onerror = function (evt, e) {
console.log('Error occured: ' + evt.data);
}; //初始化
$("#danmu").danmu({
left:0,
top:0,
height:"100%",
width:"100%",
speed:20000,
opacity:1,
font_size_small:16,
font_size_big:24,
top_botton_danmu_time:6000
});
//一个定时器,监视弹幕时间并更新到页面上
function timedCount(){
$("#time").text($('#danmu').data("nowTime")); t=setTimeout("timedCount()",50) }
timedCount(); function starter(){
$('#danmu').danmu('danmuStart');
}
function pauser(){
$('#danmu').danmu('danmuPause');
}
function resumer(){
$('#danmu').danmu('danmuResume');
}
function stoper(){
$('#danmu').danmu('danmuStop');
}
function getime(){
alert($('#danmu').data("nowTime"));
}
function getpaused(){
alert($('#danmu').data("paused"));
} //发送弹幕,使用了文档README.md第7节中推荐的方法
function send(){
var text = document.getElementById('text').value;
var color = document.getElementById('color').value;
var position = document.getElementById('position').value;
//var time = $('#danmu').data("nowTime")+1;
var size =document.getElementById('text_size').value;
//var text_obj='{ "text":"'+text+'","color":"'+color+'","size":"'+size+'","position":"'+position+'","time":'+time+'}';
//为了处理简单,方便后续加time,和isnew,就先酱紫发一半吧。
//注:time为弹幕出来的时间,isnew为是否加边框,自己发的弹幕,常理上来说是有边框的。
var text_obj='{ "text":"'+text+'","color":"'+color+'","size":"'+size+'","position":"'+position+'"';
//利用websocket发送
websocket.send(text_obj);
//清空相应的内容
document.getElementById('text').value='';
}
//调整透明度函数
function op(){
var op=document.getElementById('op').value;
$('#danmu').danmu("setOpacity",op/100);
} //调隐藏 显示
function changehide() {
var op = document.getElementById('op').value;
op = op / 100;
if (document.getElementById("ishide").checked) {
$("#danmu").danmu("setOpacity",1)
} else {
$("#danmu").danmu("setOpacity",0) }
} //设置弹幕时间
function settime(){
var t=document.getElementById("set_time").value;
t=parseInt(t)
$('#danmu').danmu("setTime",t);
}
</script> </body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192

上述代码需要注意的是websocket的建立和接收,以及send方法中对弹幕的处理。

ws_server.php

<?php
//创建websocket服务器对象,监听0.0.0.0:9505端口
$ws = new swoole_websocket_server("0.0.0.0", 9505); //监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
//var_dump($request->fd, $request->get, $request->server);
//相当于记录一个日志吧,有连接时间和连接ip
echo $request->fd.'-----time:'.date("Y-m-d H:i:s",$request->server['request_time']).'--IP--'.$request->server['remote_addr'].'-----';
}); //监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
//记录收到的消息,可以写到日志文件中
echo "Message: {$frame->data}\n"; //遍历所有连接,循环广播
foreach($ws->connections as $fd){
//如果是某个客户端,自己发的则加上isnew属性,否则不加
if($frame->fd == $fd){
$ws->push($frame->fd, $frame->data.',"isnew":""');
}else{
$ws->push($fd, "{$frame->data}");
}
}
}); //监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
echo "client-{$fd} is closed\n";
}); $ws->start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

运行方法:

输入php ws_server.php 先启动服务器端的websocket。如果要后台运行,且不随用户终端关闭而断开,需要创建一个log.txt用于存取上述输出的东西,然后输入nohup php ws_server.php > log.txt & 即可。

然后,

注,如果要用此项目,需要自行修改自己的服务器ip地址。只需要修改var wsServer = 'ws://123.206.61.229:9505'; 处即可,后台代码不需要做任何处理。

github地址:https://github.com/diligentyang/danmu

原文博主:http://blog.csdn.net/qq_28602957

如需转载请明示。

【PHP】五分钟教你编写一个实时弹幕网站的更多相关文章

  1. GC算法精解(五分钟教你终极算法---分代搜集算法)

    GC算法精解(五分钟教你终极算法---分代搜集算法) 引言 何为终极算法? 其实就是现在的JVM采用的算法,并非真正的终极.说不定若干年以后,还会有新的终极算法,而且几乎是一定会有,因为LZ相信高人们 ...

  2. 3分钟教你做一个iphone手机浏览器

    3分钟教你做一个iphone手机浏览器 第一步:新建一个Single View工程: 第二步:新建好工程,关闭arc. 第三步:拖放一个Text Field 一个UIButton 和一个 UIWebV ...

  3. JAVA WEB快速入门之从编写一个JSP WEB网站了解JSP WEB网站的基本结构、调试、部署

    接上篇<JAVA WEB快速入门之环境搭建>,在完成了环境搭建后(JDK.Tomcat.IDE),现在是万事具备,就差写代码了,今天就来从编写一个JSP WEB网站了解JSP WEB网站的 ...

  4. 不可思议的hexo,五分钟教你免费搭一个高逼格技术博客

    引言 作为程序员拥有一个属于自己的个人技术博客,绝对是百利无一害的事,不仅方便出门装b,面试时亮出博客地址也会让面试官对你的好感度倍增.经常能在很多大佬的技术文章的文末,看到这样一句话: " ...

  5. 五分钟,手撸一个Spring容器!

    大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌. 这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开S ...

  6. 手把手教你编写一个具有基本功能的shell(已开源)

    刚接触Linux时,对shell总有种神秘感:在对shell的工作原理有所了解之后,便尝试着动手写一个shell.下面是一个从最简单的情况开始,一步步完成一个模拟的shell(我命名之为wshell) ...

  7. 手把手教你编写一个简单的PHP模块形态的后门

    看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web ...

  8. Hexo+NexT(六):手把手教你编写一个Hexo过滤器插件

    Hexo+NexT介绍到这里,我认为已经可以很好地完成任务了.它所提供的一些基础功能及配置,都已经进行了讲解.你已经可以随心所欲地配置一个自己的博客环境,然后享受码字的乐趣. 把博客托管到Github ...

  9. 一分钟教你编写Linux全局内置命令

    前言:在linux命令使用中,有些命令总是又长又难记,就算是经常使用的命令每次都敲也真的很烦,所以今天教大家一个方法,来简化命令,创建我们自己的内建命令!!! 创建内置命令 创建命令存储目录 现在li ...

随机推荐

  1. 【BZOJ4300】 绝世好题

    傻逼题都不能一眼看出思路…… 原题: 给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len). n<=100000,ai&l ...

  2. vue中在页面渲染完之后获取元素(否则动态渲染的元素获取不到)

    两种方法: 方法一: 使用$nextTick,在异步获得数据之后再获取元素: 方法二: 在then之后再获取该元素: 问题2:vue中监听改变数组的方法: let idx =; this.listIn ...

  3. WebSafeBase64Decode

    WebSafeBase64Decode golang (adapter zplay doubleclick ) func base64url_decode(s string) ([]byte, err ...

  4. Kettle入门--作业和转换的使用

    本来想在centos7下部署的,发现因为java版本的问题,无法成功部署,无奈,转到windows平台(后来找到解决方法了,在centos7系统下yum install webkitgtk* -y 就 ...

  5. ethr 微软开源的tcp udp http 网络性能测试工具

    ethr 是微软开源的tcp udp http 网络性能测试工具包包含的server 以及 client 我们可以远程测试 同时对于https icmp 的支持也在开发中,tcp 协议支持连接.带宽. ...

  6. 13机器学习实战之PCA(1)

    降维技术 对数据进行降维有如下一系列的原因: 使得数据集更容易使用 降低很多算法的计算开销 去除噪音 使得结果易懂 在以下3种降维技术中, PCA的应用目前最为广泛,因此本章主要关注PCA. 主成分分 ...

  7. Where关键词的用法

    where(泛型类型约束) where关键词一个最重要的用法就是在泛型的声明.定义中做出约束. 约束又分为接口约束.基类约束.构造函数约束.函数方法的约束,我们慢慢介绍. 接口约束 顾名思义,泛型参数 ...

  8. C语言打印100到200之间的素数

    用C语言打印素数,我们首先要了素数的相关定义:只有1和它本身两个因数的自然数,也就是说除了1和它本身外,不能被其他自然数整除的数就称为素数. 例如:101只能被1 和101 整除:103只能被1 和1 ...

  9. Node。js 访问gmail

    参考: https://developers.google.com/gmail/api/quickstart/nodejs step 1,在google网站上打开gmail api,下载JSOn st ...

  10. C++Builder XE7 中“匿名”方法实现

    class TMyProc : public TCppInterfacedObject<TThreadProcedure> { private: String p1; String p2; ...