天天看點

如何使用PHP建構一個高性能的彈幕後端服務環境依賴

轉載自:https://zhuanlan.zhihu.com/p/23992890

随着WEB2.0的流行,現在很多網站都流行使用“彈幕”這種形式來實作互動。

彈幕(barrage),中文流行詞語,原意指用大量或少量火炮提供密集炮擊。大量以字幕彈(dàn)出形式顯示的評論同時在螢幕上飄過的現象也被稱為彈幕。

作為PHPer的我們,看到現在各種網站都有酷炫的彈幕飛過,我們是不是也想給自己的網站加入彈幕功能呢?

首先彈幕的後端其實說白了和公共聊天室的後端原理十分相似,都是一個用戶端發送消息給服務端,服務端再将收到的消息廣播給其他的用戶端。對于後端來說他們幾乎沒差別,差別就在于前端。

好在我們有一個前端彈幕插件,這個插件是一個jquery插件,github位址:https://github.com/chiruom/jquery.danmu.js,基本上會使用jquery文法,看看示例代碼就可以傻瓜化使用。

前端已經有了解決方案,但是後端呢?前端如何與後端通訊?用傳統的ajax輪詢嗎?不行,這樣效率太低,想想各大火爆的直播平台都是同一時間幾萬人線上,幾千人同時發彈幕,如果靠ajax輪詢一個php接口的話伺服器會吃不消的。且彈幕消息存儲方案略顯複雜,有人問為什麼要存儲呢?因為ajax使用的HTTP協定是無狀态協定,A用戶端和B用戶端之間對于伺服器來說沒有任何标志,如果伺服器要確定A用戶端和B用戶端分别在兩次請求的時候伺服器隻傳回這兩個用戶端沒有擷取過的彈幕消息,那麼伺服器端就必須使用一個緩存來辨別某某用戶端看過哪條彈幕消息。綜上所述ajax可以實作小規模的彈幕通信方案,但是很麻煩。

好在最新的HTML5中加入了WebSocket協定,我們可以通過WebSocket這種基于HTTP協定之上的即時通信協定來替代ajax這種傳統的我問你答的老舊通信模式。而我們是PHPer,對于我們這種隻懂PHP的人該如何編寫WebSocket服務端呢?好在我們又得知PHP有一個Swoole擴充,我們在PHP語言中使用它可以很友善的建構一個WebSocket服務端。

關于Swoole的介紹可以參照他的官網http://www.swoole.com/,下面引用官網對它的一段簡短的介紹。

PHP的異步、并行、高性能網絡通信引擎,使用純C語言編寫,提供了PHP語言的異步多線程伺服器,異步TCP/UDP網絡用戶端,異步MySQL,異步Redis,資料庫連接配接池,AsyncTask,消息隊列,毫秒定時器,異步檔案讀寫,異步DNS查詢。 Swoole内置了Http/WebSocket伺服器端/用戶端、Http2.0伺服器端。

Swoole可以廣泛應用于網際網路、移動通信、企業軟體、雲計算、網絡遊戲、物聯網(IOT)、車聯網、智能家居等領域。 使用PHP+Swoole作為網絡通信架構,可以使企業IT研發團隊的效率大大提升,更加專注于開發創新産品。

先别被Swoole這麼多的功能吓到了。我們先關注這裡面的重點

Swoole内置了Http/WebSocket伺服器端/用戶端

意味着我們可以通過它建構WebSocket的服務端。看到這裡我們是不是就急急忙忙去拿官網的WebSocket服務端代碼做測試呢?不,Swoole是一個PHP擴充,意味着我們還得去安裝它。是不是直接去下載下傳so檔案然後在php.ini中加入extension=swoole.so就可以了呢?還不是,我們先去看看Swoole擴充的依賴,這也是我們使用任何語言的任何外部包,外部子產品,外部擴充最先要了解的問題。

參考官網:http://wiki.swoole.com/wiki/page/7.html

環境依賴

  • 僅支援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那麼說明安裝成功了。

接下來就正式開始我們的編碼旅程了。

開始編碼旅程之前我們先看看最基礎的效果原型是什麼樣子

如何使用PHP建構一個高性能的彈幕後端服務環境依賴

沒錯就是這個樣子,兩個浏覽器之前完全獨立使用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('(' + 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 首發,轉載請注明出處,謝謝。