扣丁學(xué)堂PHP培訓(xùn)之五分鐘教你編寫一個(gè)實(shí)時(shí)彈幕網(wǎng)站
2018-01-16 10:59:55
1704瀏覽
在現(xiàn)如今,隨著互聯(lián)網(wǎng)時(shí)代飛速的發(fā)展,比如目前較火的直播平臺(tái)都有實(shí)時(shí)彈幕滾動(dòng),而小編首先要搞定的是前端頁(yè)面,最起碼得有個(gè)框,那么問(wèn)題來(lái)了,這些視頻彈幕網(wǎng)站如何做到實(shí)時(shí)同步的?PHP如何開(kāi)發(fā)一個(gè)類似的網(wǎng)站?
經(jīng)過(guò)搜索,找到了一個(gè)jQuery.danmu.js的開(kāi)源項(xiàng)目??戳艘幌聅tar的人還挺多github.com/chiruom/jquery.danmu.js
原來(lái)是源文件中的jQuery插件的問(wèn)題。在src目錄下,并沒(méi)有該文件
<scriptsrc="../src/jquery-2.1.4.min.js"></script>
算了還是調(diào)用百度的在線jQuery插件吧
<scriptsrc="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
再一刷新,不出預(yù)料,成功運(yùn)行。
后端,那就先來(lái)說(shuō)說(shuō)彈幕的原理吧。彈幕,就相當(dāng)于一個(gè)公共聊天室,都是一個(gè)客戶端發(fā)送消息給服務(wù)端,服務(wù)端再將收到的消息廣播給其他的客戶端。
用傳統(tǒng)的ajax輪詢嗎?不行,這樣效率太低,想想各大火爆的直播平臺(tái)都是同一時(shí)間幾萬(wàn)人在線,幾千人同時(shí)發(fā)彈幕,如果靠ajax輪詢一個(gè)PHP接口的話服務(wù)器會(huì)吃不消的。且彈幕消息存儲(chǔ)方案略顯復(fù)雜,有人問(wèn)為什么要存儲(chǔ)呢?因?yàn)閍jax使用的HTTP協(xié)議是無(wú)狀態(tài)協(xié)議,A客戶端和B客戶端之間對(duì)于服務(wù)器來(lái)說(shuō)沒(méi)有任何標(biāo)志,如果服務(wù)器要確保A客戶端和B客戶端分別在兩次請(qǐng)求的時(shí)候服務(wù)器只返回這兩個(gè)客戶端沒(méi)有獲取過(guò)的彈幕消息,那么服務(wù)器端就必須使用一個(gè)緩存來(lái)標(biāo)識(shí)某某客戶端看過(guò)哪條彈幕消息。綜上所述ajax可以實(shí)現(xiàn)小規(guī)模的彈幕通信方案,但是很麻煩。
好在最新的HTML5中加入了WebSocket協(xié)議,我們可以通WebSocket這種基于HTTP協(xié)議之上的即時(shí)通信協(xié)議來(lái)替代ajax這種傳統(tǒng)的我問(wèn)你答的老舊通信模式。而我們是PHPer,對(duì)于我們這種只懂PHP的人該如何編寫WebSocket服務(wù)端呢?好在我們又得知PHP有一個(gè)Swoole擴(kuò)展,我們?cè)赑HP語(yǔ)言中使用它可以很方便的構(gòu)建一個(gè)WebSocket服務(wù)端。
關(guān)于Swoole,下面這段是其官網(wǎng)上的話:
PHP的異步、并行、高性能網(wǎng)絡(luò)通信引擎,使用純C語(yǔ)言編寫,提供了PHP語(yǔ)言的異步多線程服務(wù)器,異步TCP/UDP網(wǎng)絡(luò)客戶端,異步MySQL,異步Redis,數(shù)據(jù)庫(kù)連接池,AsyncTask,消息隊(duì)列,毫秒定時(shí)器,異步文件讀寫,異步DNS查詢。Swoole內(nèi)置了Http/WebSocket服務(wù)器端/客戶端、Http2.0服務(wù)器端。
Swoole可以廣泛應(yīng)用于互聯(lián)網(wǎng)、移動(dòng)通信、企業(yè)軟件、云計(jì)算、網(wǎng)絡(luò)游戲、物聯(lián)網(wǎng)(IOT)、車聯(lián)網(wǎng)、智能家居等領(lǐng)域。使用PHP+Swoole作為網(wǎng)絡(luò)通信框架,可以使企業(yè)IT研發(fā)團(tuán)隊(duì)的效率大大提升,更加專注于開(kāi)發(fā)創(chuàng)新產(chǎn)品。
還有一個(gè)問(wèn)題需要解決,那就是,這個(gè)jquery.danmu.js是基于彈幕運(yùn)行時(shí)間的一個(gè)插件。那又要如何做到實(shí)時(shí)呢。開(kāi)始博主想的是在服務(wù)器端規(guī)定一個(gè)時(shí)間(即其連接時(shí)間),當(dāng)有客戶端連接時(shí),返回服務(wù)器的當(dāng)前時(shí)間戳,然后以此為依據(jù)開(kāi)始計(jì)時(shí)。但是遇到的問(wèn)題如下:
該彈幕插件是按十分之秒計(jì)時(shí)制度。
各瀏覽器上js的定時(shí)器的運(yùn)行時(shí)間略有差異。
時(shí)間不能完全同步。
websocket是實(shí)時(shí)通信的,哎,那所有客戶端的時(shí)間,不一致就不一致吧,彈幕發(fā)的時(shí)間根據(jù)各個(gè)客戶端的為準(zhǔn)唄,都以當(dāng)前各個(gè)客戶端的時(shí)間來(lái)發(fā),websocket只傳遞不包含時(shí)間的數(shù)據(jù)(好吧有點(diǎn)繞,我自己都感覺(jué)說(shuō)饒了),咱們直接來(lái)上代碼吧。
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>
<!--黑背景和彈幕區(qū)-->
<divid="danmuarea">
<divid="danmu">
</div>
</div>
<!--控制區(qū)-->
<divclass="ctr">
<buttontype="button"onclick="pauser()">彈幕暫停</button>
<buttontype="button"onclick="resumer()">彈幕繼續(xù)</button>
顯示彈幕:<inputtype='checkbox'checked='checked'id='ishide'value='is'onchange='changehide()'>
彈幕透明度:
<inputtype="range"name="op"id="op"onchange="op()"value="100"><br>
當(dāng)前彈幕運(yùn)行時(shí)間(秒):<spanid="time"></span>
<!--設(shè)置當(dāng)前彈幕時(shí)間(秒):<inputtype="text"id="set_time"max=20/>
<buttontype="button"onclick="settime()">設(shè)置</button>-->
<br>
發(fā)彈幕:
<selectname="color"id="color">
<optionvalue="white">白色</option>
<optionvalue="red">紅色</option>
<optionvalue="green">綠色</option>
<optionvalue="blue">藍(lán)色</option>
<optionvalue="yellow">黃色</option>
</select>
<selectname="size"id="text_size">
<optionvalue="1">大文字</option>
<optionvalue="0">小文字</option>
</select>
<selectname="position"id="position">
<optionvalue="0">滾動(dòng)</option>
<optionvalue="1">頂端</option>
<optionvalue="2">底端</option>
</select>
<inputtype="textarea"id="text"max=300/>
<buttontype="button"onclick="send()">發(fā)送</button>
</div>
<script>
//WebSocket
varwsServer='ws://123.206.61.229:9505';
varwebsocket=newWebSocket(wsServer);
websocket.onopen=function(evt){
console.log("ConnectedtoWebSocketserver.");
/*websocket.send("gaga");*/
//連上之后就打開(kāi)彈幕
$('#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+'}';//獲取加上當(dāng)前時(shí)間
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
});
//一個(gè)定時(shí)器,監(jiān)視彈幕時(shí)間并更新到頁(yè)面上
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"));
}
//發(fā)送彈幕,使用了文檔README.md第7節(jié)中推薦的方法
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+'}';
//為了處理簡(jiǎn)單,方便后續(xù)加time,和isnew,就先醬紫發(fā)一半吧。
//注:time為彈幕出來(lái)的時(shí)間,isnew為是否加邊框,自己發(fā)的彈幕,常理上來(lái)說(shuō)是有邊框的。
vartext_obj='{"text":"'+text+'","color":"'+color+'","size":"'+size+'","position":"'+position+'"';
//利用websocket發(fā)送
websocket.send(text_obj);
//清空相應(yīng)的內(nèi)容
document.getElementById('text').value='';
}
//調(diào)整透明度函數(shù)
functionop(){
varop=document.getElementById('op').value;
$('#danmu').danmu("setOpacity",op/100);
}
//調(diào)隱藏顯示
functionchangehide(){
varop=document.getElementById('op').value;
op=op/100;
if(document.getElementById("ishide").checked){
$("#danmu").danmu("setOpacity",1)
}else{
$("#danmu").danmu("setOpacity",0)
}
}
//設(shè)置彈幕時(shí)間
functionsettime(){
vart=document.getElementById("set_time").value;
t=parseInt(t)
$('#danmu').danmu("setTime",t);
}
</script>
</body>
</html>
上述代碼需要注意的是websocket的建立和接收,以及send方法中對(duì)彈幕的處理。
ws_server.php
<?php
//創(chuàng)建websocket服務(wù)器對(duì)象,監(jiān)聽(tīng)0.0.0.0:9505端口
$ws=newswoole_websocket_server("0.0.0.0",9505);
//監(jiān)聽(tīng)WebSocket連接打開(kāi)事件
$ws->on('open',function($ws,$request){
//var_dump($request->fd,$request->get,$request->server);
//相當(dāng)于記錄一個(gè)日志吧,有連接時(shí)間和連接ip
echo$request->fd.'-----time:'.date("Y-m-dH:i:s",$request->server['request_time']).'--IP--'.$request->server['remote_addr'].'-----';
});
//監(jiān)聽(tīng)WebSocket消息事件
$ws->on('message',function($ws,$frame){
//記錄收到的消息,可以寫到日志文件中
echo"Message:{$frame->data}\n";
//遍歷所有連接,循環(huán)廣播
foreach($ws->connectionsas$fd){
//如果是某個(gè)客戶端,自己發(fā)的則加上isnew屬性,否則不加
if($frame->fd==$fd){
$ws->push($frame->fd,$frame->data.',"isnew":""');
}else{
$ws->push($fd,"{$frame->data}");
}
}
});
//監(jiān)聽(tīng)WebSocket連接關(guān)閉事件
$ws->on('close',function($ws,$fd){
echo"client-{$fd}isclosed\n";
});
$ws->start();
運(yùn)行方法:
輸入phpws_server.php先啟動(dòng)服務(wù)器端的websocket。如果要后臺(tái)運(yùn)行,且不隨用戶終端關(guān)閉而斷開(kāi),需要?jiǎng)?chuàng)建一個(gè)log.txt用于存取上述輸出的東西,然后輸入nohupphpws_server.php>log.txt&即可。
注,如果要用此項(xiàng)目,需要自行修改自己的服務(wù)器ip地址。只需要修改varwsServer='ws://123.206.61.229:9505';處即可,后臺(tái)代碼不需要做任何處理。
最后希望對(duì)同學(xué)們有所幫助。想要學(xué)習(xí)PHP培訓(xùn)技術(shù)的小伙伴歡迎選擇扣丁學(xué)堂進(jìn)行學(xué)習(xí)。扣丁學(xué)堂不僅有專業(yè)的老師和與時(shí)俱進(jìn)的課程體系,還有大量的PHP視頻教程供學(xué)員觀看學(xué)習(xí),想要學(xué)到實(shí)用技術(shù)高薪就業(yè)的小伙伴抓緊時(shí)間行動(dòng)吧??鄱W(xué)堂PHP技術(shù)交流群:374332265。
【關(guān)注微信公眾號(hào)獲取更多的學(xué)習(xí)資料】
查看更多關(guān)于“php培訓(xùn)資訊”的相關(guān)文章>>
標(biāo)簽:
PHP培訓(xùn)
PHP視頻教程
PHP從入門到精通
PHP學(xué)習(xí)路線圖
PHP開(kāi)發(fā)工程師