彈幕(barrage),中文流行詞語,原意指用大量或少許火炮提供密集炮擊。大量以字幕彈(dàn)出形式顯示的評論同時在屏幕上飄過的現象也被稱爲彈幕。
php
做爲PHPer的咱們,看到如今各類網站都有酷炫的彈幕飛過,咱們是否是也想給本身的網站加入彈幕功能呢?html
首先彈幕的後端其實說白了和公共聊天室的後端原理十分類似,都是一個客戶端發送消息給服務端,服務端再將收到的消息廣播給其餘的客戶端。對於後端來講他們幾乎沒區別,區別就在於前端。前端
好在咱們有一個前端彈幕插件,這個插件是一個jquery插件,github地址:https://github.com/chiruom/jquery.danmu.js,基本上會使用jquery語法,看看示例代碼就能夠傻瓜化使用。jquery
前端已經有了解決方案,可是後端呢?前端如何與後端通信?用傳統的ajax輪詢嗎?不行,這樣效率過低,想一想各大火爆的直播平臺都是同一時間幾萬人在線,幾千人同時發彈幕,若是靠ajax輪詢一個php接口的話服務器會吃不消的。且彈幕消息存儲方案略顯複雜,有人問爲何要存儲呢?由於ajax使用的HTTP協議是無狀態協議,A客戶端和B客戶端之間對於服務器來講沒有任何標誌,若是服務器要確保A客戶端和B客戶端分別在兩次請求的時候服務器只返回這兩個客戶端沒有獲取過的彈幕消息,那麼服務器端就必須使用一個緩存來標識某某客戶端看過哪條彈幕消息。綜上所述ajax能夠實現小規模的彈幕通訊方案,可是很麻煩。linux
好在最新的HTML5中加入了WebSocket協議,咱們能夠經過WebSocket這種基於HTTP協議之上的即時通訊協議來替代ajax這種傳統的我問你答的老舊通訊模式。而咱們是PHPer,對於咱們這種只懂PHP的人該如何編寫WebSocket服務端呢?好在咱們又得知PHP有一個Swoole擴展,咱們在PHP語言中使用它能夠很方便的構建一個WebSocket服務端。git
關於Swoole的介紹能夠參照他的官網http://www.swoole.com/,下面引用官網對它的一段簡短的介紹。github
PHP的異步、並行、高性能網絡通訊引擎,使用純C語言編寫,提供了PHP語言的異步多線程服務器,異步TCP/UDP網絡客戶端,異步MySQL,異步Redis,數據庫鏈接池,AsyncTask,消息隊列,毫秒定時器,異步文件讀寫,異步DNS查詢。 Swoole內置了Http/WebSocket服務器端/客戶端、Http2.0服務器端。先別被Swoole這麼多的功能嚇到了。咱們先關注這裏面的重點
Swoole能夠普遍應用於互聯網、移動通訊、企業軟件、雲計算、網絡遊戲、物聯網(IOT)、車聯網、智能家居等領域。 使用PHP+Swoole做爲網絡通訊框架,可使企業IT研發團隊的效率大大提高,更加專一於開發創新產品。
Swoole內置了Http/WebSocket服務器端/客戶端
意味着咱們能夠經過它構建WebSocket的服務端。看到這裏咱們是否是就急急忙忙去拿官網的WebSocket服務端代碼作測試呢?不,Swoole是一個PHP擴展,意味着咱們還得去安裝它。是否是直接去下載so文件而後在php.ini中加入extension=swoole.so就能夠了呢?還不是,咱們先去看看Swoole擴展的依賴,這也是咱們使用任何語言的任何外部包,外部模塊,外部擴展最早要了解的問題。web
參考官網:http://wiki.swoole.com/wiki/page/7.htmlajax
環境依賴
- 僅支持Linux,FreeBSD,MacOS,3類操做系統
- Linux內核版本2.3.32以上
- PHP5.3.10以上版本,包括PHP7
- gcc4.4以上版本或者clang
- cmake2.4+,編譯爲libswoole.so做爲C/C++庫時須要使用cmake
PHP版本依賴
- swoole僅支持PHP5.3.10或更高版本,建議使用PHP5.4+
- swoole不依賴php的stream、sockets、pcntl、posix、sysvmsg等擴展。PHP只需安裝最基本的擴展便可
意味着咱們Windows下是沒法使用這個擴展了(其實能夠藉助cygwin在win下使用swoole,可是考慮到咱們使用swoole擴展就是爲了性能,也爲了熟悉之後的生產環節部署作準備,強烈推薦在linux下開發),那麼咱們把開發環境轉移到Linux下進行吧。數據庫
接着還要求Linux內核版本爲2.3.32以上,PHP爲5.3.10以上,那麼咱們就用最新的CentOS吧,這個版本的yum安裝的php直接就是PHP7最新版,根本無需考慮其餘問題,固然你喜歡圖形界面,用Ubuntu也能夠。其餘的基本上最新的Linux發行版都是符合版本要求的。
接着咱們便來安裝這個擴展,推薦使用PECL來安裝,只須要一條
pecl install swoole
便可,很是方便。固然你要編譯安裝,具體步驟請參考http://wiki.swoole.com/wiki/page/6.html
安裝完擴展以後在命令行下輸入
php -m
檢查,若是有swoole那麼說明安裝成功了。
接下來就正式開始咱們的編碼旅程了。
開始編碼旅程以前咱們先看看最基礎的效果原型是什麼樣子
沒錯就是這個樣子,兩個瀏覽器以前徹底獨立使用Websocket鏈接服務端,所以對於服務端來講這兩個瀏覽器就至關於兩個徹底處在不一樣機器上的客戶端。
效果看完了就開始來說代碼吧。
咱們先看看官網的WebSocket服務端示例代碼。
$serv = new Swoole\Websocket\Server("127.0.0.1", 9502); $serv->on('Open', function($server, $req) { echo "connection open: ".$req->fd; }); $serv->on('Message', function($server, $frame) { echo "message: ".$frame->data; $server->push($frame->fd, json_encode(["hello", "world"])); }); $serv->on('Close', function($server, $fd) { echo "connection close: ".$fd; }); $serv->start();
咱們看到這個代碼的第一行先是new了一個WebSocket服務端對象,而且在構造方法中的第一個參數指定了服務端監聽的IP,第二個參數指定了服務端監聽的端口。而後使用on方法爲每個事件設置了回調函數,最後一行start方法正式開始運行服務端。
這種寫法很是像Javascript裏面的異步調用,這也是Swoole中的事件驅動異步非阻塞特性,正由於是這種特性,每個獨立的事件(請求)會在服務端接收到以後分別異步處理,他們之間無需互相等待,這也是Swoole性能高的緣由所在。
咱們來分別剖析一下每個事件的含義。
$serv->on('Open', function($server, $req) { echo "connection open: ".$req->fd; });
顧名思義,Open表示打開一個新的連接,而且在事件觸發以後echo出鏈接上服務端的客戶端id,該客戶端惟一id爲回調函數第二個參數中的fd字段。這也是服務端區分客戶端的惟一id。
$serv->on('Message', function($server, $frame) { echo "message: ".$frame->data; $server->push($frame->fd, json_encode(["hello", "world"])); });
一樣顧名思義,Message表示消息到達服務端的事件,而且在事件觸發以後echo出發送給服務端的數據,該數據爲回調函數第二個參數的data字段。另外咱們還看到它調用了$server->push,這是回調函數的第一個參數中的push方法,它是一個服務端給客戶的發送數據的方法,第一個參數爲要發送的客戶端id,第二個爲要發送的數據,這裏的含義是向發給服務端消息的那個客戶端發送["hello", "world"]這個數組(方括號寫數組爲PHP5.4的新特性,若是你是PHP5.3請使用傳統的array工廠函數生成數組)通過json序列化以後的數據。
$serv->on('Close', function($server, $fd) { echo "connection close: ".$fd; });
最後一個事件Close更加容易理解,就是關閉事件,固然關閉的不是服務端,而是客戶端,能夠理解爲客戶端與服務端斷開鏈接的事件。回調函數中的代碼含義爲echo出與服務端斷開鏈接的那個客戶端id。
基本的API都清楚了,下面就直接看代碼吧,短短二十行而已。
https://github.com/cw1997/danmu-demo/blob/master/server.php
$server = new swoole_websocket_server("0.0.0.0", 1997); $server->on('open', function (swoole_websocket_server $server, $request) { echo "server: handshake success with fd{$request->fd}\n";//$request->fd 是客戶端id }); $server->on('message', function (swoole_websocket_server $server, $frame) { echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n"; //$frame->fd 是客戶端id,$frame->data是客戶端發送的數據 //服務端向客戶端發送數據是用 $server->push( '客戶端id' , '內容') $data = $frame->data; foreach($server->connections as $fd){ $server->push($fd , $data);//循環廣播 } }); $server->on('close', function ($ser, $fd) { echo "client {$fd} closed\n"; }); $server->start();
這裏最核心的廣播代碼其實還用到了一個以前沒有提到過的成員,也就是swoole_websocket_server對象的connections成員,這個成員中保存了全部已鏈接上該WebSocket服務端的fd,也就是客戶端id。所以咱們只要在message事件中使用foreach遍歷該成員,循環將全部服務端收到的彈幕消息都發送給其餘已鏈接上該服務端的客戶端便可。
後端講完了再講講前端吧。
前端代碼也不是不少https://github.com/cw1997/danmu-demo/blob/master/index.html
var ws = new WebSocket("ws://192.168.1.107:1997"); ws.onopen = function(){ console.log("握手成功"); ws.send('hello world!!!'); }; ws.onmessage = function(e){ console.log("message:" + e.data); var time = jQuery('#danmu').data("nowtime") + 1; var text_obj = '{ "text":"' + e.data + '" , "color":"green" ,"size":"1","position":"0","time":"' + time + '" ,"isnew":" "}'; //構造加上了innew屬性的字符串danmu對象 console.log(text_obj); var new_obj = eval_r('(' + text_obj + ')'); //轉化爲js對象 jQuery('#danmu').danmu("add_danmu", new_obj); //向插件中添加該danmu對象 }; ws.onerror = function(){ console.log("error"); };
核心代碼都在這裏,使用new WebSocket("ws://192.168.1.107:1997")建立一個WebSocket客戶端鏈接對象,經過該對象的各類事件進行對應的操做,和服務端是否是很像?更多代碼解釋能夠參考源代碼中的註釋,這裏不作更多介紹。
看到這裏相信做爲一名PHPer的你也能夠開發出屬於本身的彈幕系統了。這裏展現的只是一個最基礎最原始的彈幕平臺。咱們也瞭解到了使用PHP開發一個彈幕平臺須要涉及到的技術有WebSocket,Swoole擴展,甚至碰到了不少初級開發者平時不怎麼接觸的工具,好比說PECL,好比說Linux。
其實PHP結合Swoole擴展還能夠作不少事情,好比說對接各類家電,對接各類硬件接口實如今Web端實時控制家電,又好比說結合樹莓派作智能小車,經過web端進行遙控等等,各類新奇的玩法等你發現。誰說PHP只能作Web開發?PHP擁有了Swoole擴展其實能作的事情還有不少,Swoole就像他的宣傳標題同樣:從新定義PHP。
本文章由 @昌維 原創,在知乎專欄-代碼之美 https://zhuanlan.zhihu.com/codes 首發,轉載請註明出處,謝謝。