扣丁学堂PHP培训之五分钟教你编写一个实时弹幕网站
在现如今,随着互联网时代飞速的发展,比如目前较火的直播平台都有实时弹幕滚动,而小编首先要搞定的是前端页面,最起码得有个框,那么问题来了,这些视频弹幕网站如何做到实时同步的?PHP如何开发一个类似的网站?
经过搜索,找到了一个jQuery.danmu.js的开源项目。看了一下star的人还挺多github.com/chiruom/jquery.danmu.js
进入demo目录,先运行一下例子看看结果呗。
果然,点开以后出现了一个高大上的页面,略看一下功能还挺多。但是问题来了,为啥我点击开始,一点反应也木有呢。
原来是源文件中的jQuery插件的问题。在src目录下,并没有该文件
<scriptsrc="../src/jquery-2.1.4.min.js"></script>
算了还是调用百度的在线jQuery插件吧
<scriptsrc="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
再一刷新,不出预料,成功运行。
后端,那就先来说说弹幕的原理吧。弹幕,就相当于一个公共聊天室,都是一个客户端发送消息给服务端,服务端再将收到的消息广播给其他的客户端。
用传统的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研发团队的效率大大提升,更加专注于开发创新产品。
还有一个问题需要解决,那就是,这个jquery.danmu.js是基于弹幕运行时间的一个插件。那又要如何做到实时呢。开始博主想的是在服务器端规定一个时间(即其连接时间),当有客户端连接时,返回服务器的当前时间戳,然后以此为依据开始计时。但是遇到的问题如下:
该弹幕插件是按十分之秒计时制度。
各浏览器上js的定时器的运行时间略有差异。
时间不能完全同步。
websocket是实时通信的,哎,那所有客户端的时间,不一致就不一致吧,弹幕发的时间根据各个客户端的为准呗,都以当前各个客户端的时间来发,websocket只传递不包含时间的数据(好吧有点绕,我自己都感觉说饶了),咱们直接来上代码吧。
index.html
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width,initial-scale=1.0">
<title>弹幕madebydiligentyang</title>
<style>
body{
font-family:"MicrosoftYaHei"!important;
font-color:#222;
}
pre{
line-height:2em;
font-family:"MicrosoftYaHei"!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>
<scriptsrc="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<scriptsrc="../dist/jquery.danmu.min.js"></script>
</head>
<bodyclass="center">
Demo<br><br>
<!--黑背景和弹幕区-->
<divid="danmuarea">
<divid="danmu">
</div>
</div>
<!--控制区-->
<divclass="ctr">
<buttontype="button"onclick="pauser()">弹幕暂停</button>&nbsp;&nbsp;&nbsp;&nbsp;
<buttontype="button"onclick="resumer()">弹幕继续</button>&nbsp;&nbsp;&nbsp;&nbsp;
显示弹幕:<inputtype='checkbox'checked='checked'id='ishide'value='is'onchange='changehide()'>&nbsp;&nbsp;&nbsp;&nbsp;
弹幕透明度:
<inputtype="range"name="op"id="op"onchange="op()"value="100"><br>
当前弹幕运行时间(秒):<spanid="time"></span>&nbsp;&nbsp;
<!--设置当前弹幕时间(秒):<inputtype="text"id="set_time"max=20/>
<buttontype="button"onclick="settime()">设置</button>-->
<br>
发弹幕:
<selectname="color"id="color">
<optionvalue="white">白色</option>
<optionvalue="red">红色</option>
<optionvalue="green">绿色</option>
<optionvalue="blue">蓝色</option>
<optionvalue="yellow">黄色</option>
</select>
<selectname="size"id="text_size">
<optionvalue="1">大文字</option>
<optionvalue="0">小文字</option>
</select>
<selectname="position"id="position">
<optionvalue="0">滚动</option>
<optionvalue="1">顶端</option>
<optionvalue="2">底端</option>
</select>
<inputtype="textarea"id="text"max=300/>
<buttontype="button"onclick="send()">发送</button>
</div>
<script>
//WebSocket
varwsServer='ws://123.206.61.229:9505';
varwebsocket=newWebSocket(wsServer);
websocket.onopen=function(evt){
console.log("ConnectedtoWebSocketserver.");
/*websocket.send("gaga");*/
//连上之后就打开弹幕
$('#danmu').danmu('danmuResume');
};
websocket.onclose=function(evt){
console.log("Disconnected");
};
websocket.onmessage=function(evt){
console.log('Retrieveddatafromserver:'+evt.data);
vartime=$('#danmu').data("nowTime")+1;
vartext_obj=evt.data+',"time":'+time+'}';//获取加上当前时间
console.log(text_obj);
varnew_obj=eval('('+text_obj+')');
$('#danmu').danmu("addDanmu",new_obj);//添加弹幕
};
websocket.onerror=function(evt,e){
console.log('Erroroccured:'+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
});
//一个定时器,监视弹幕时间并更新到页面上
functiontimedCount(){
$("#time").text($('#danmu').data("nowTime"));
t=setTimeout("timedCount()",50)
}
timedCount();
functionstarter(){
$('#danmu').danmu('danmuStart');
}
functionpauser(){
$('#danmu').danmu('danmuPause');
}
functionresumer(){
$('#danmu').danmu('danmuResume');
}
functionstoper(){
$('#danmu').danmu('danmuStop');
}
functiongetime(){
alert($('#danmu').data("nowTime"));
}
functiongetpaused(){
alert($('#danmu').data("paused"));
}
//发送弹幕,使用了文档README.md第7节中推荐的方法
functionsend(){
vartext=document.getElementById('text').value;
varcolor=document.getElementById('color').value;
varposition=document.getElementById('position').value;
//vartime=$('#danmu').data("nowTime")+1;
varsize=document.getElementById('text_size').value;
//vartext_obj='{"text":"'+text+'","color":"'+color+'","size":"'+size+'","position":"'+position+'","time":'+time+'}';
//为了处理简单,方便后续加time,和isnew,就先酱紫发一半吧。
//注:time为弹幕出来的时间,isnew为是否加边框,自己发的弹幕,常理上来说是有边框的。
vartext_obj='{"text":"'+text+'","color":"'+color+'","size":"'+size+'","position":"'+position+'"';
//利用websocket发送
websocket.send(text_obj);
//清空相应的内容
document.getElementById('text').value='';
}
//调整透明度函数
functionop(){
varop=document.getElementById('op').value;
$('#danmu').danmu("setOpacity",op/100);
}
//调隐藏显示
functionchangehide(){
varop=document.getElementById('op').value;
op=op/100;
if(document.getElementById("ishide").checked){
$("#danmu").danmu("setOpacity",1)
}else{
$("#danmu").danmu("setOpacity",0)
}
}
//设置弹幕时间
functionsettime(){
vart=document.getElementById("set_time").value;
t=parseInt(t)
$('#danmu').danmu("setTime",t);
}
</script>
</body>
</html>
上述代码需要注意的是websocket的建立和接收,以及send方法中对弹幕的处理。
ws_server.php
<?php
//创建websocket服务器对象,监听0.0.0.0:9505端口
$ws=newswoole_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-dH:i:s",$request->server['request_time']).'--IP--'.$request->server['remote_addr'].'-----';
});
//监听WebSocket消息事件
$ws->on('message',function($ws,$frame){
//记录收到的消息,可以写到日志文件中
echo"Message:{$frame->data}";
//遍历所有连接,循环广播
foreach($ws->connectionsas$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}isclosed";
});
$ws->start();
运行方法:
输入phpws_server.php先启动服务器端的websocket。如果要后台运行,且不随用户终端关闭而断开,需要创建一个log.txt用于存取上述输出的东西,然后输入nohupphpws_server.php>log.txt&即可。
注,如果要用此项目,需要自行修改自己的服务器ip地址。只需要修改varwsServer='ws://123.206.61.229:9505';处即可,后台代码不需要做任何处理。
最后希望对同学们有所帮助。想要学习PHP开发技术的小伙伴欢迎选择扣丁学堂进行学习。扣丁学堂不仅有专业的老师和与时俱进的课程体系,还有大量的PHP视频教程供学员观看学习,想要学到实用技术高薪就业的小伙伴抓紧时间行动吧。扣丁学堂PHP技术交流群:374332265。