鄧緒高
摘要:在一個博客系統(tǒng)中,經(jīng)常會使用到消息推送系統(tǒng)來發(fā)送一些通知給登錄用戶。如博主需要發(fā)送某個消息給所有登錄用戶,或者某個用戶對另一個用戶的評論進行了回復(fù)等等。該文利用WebSwoole技術(shù)來實現(xiàn)點對點的消息推送提醒。
關(guān)鍵詞:PHP;WebSwoole;消息推送
中圖分類號:TP311 文獻標(biāo)識碼:A 文章編號:1009-3044(2018)25-0091-02
1 前言
B/S結(jié)構(gòu)的軟件項目中有時客戶端需要實時的獲得服務(wù)器消息,但默認HTTP協(xié)議只支持請求響應(yīng)模式。這樣做可以簡化Web服務(wù)器,減少服務(wù)器的負擔(dān),加快響應(yīng)速度,因為服務(wù)器不需要與客戶端長時間建立一個通信鏈接,但不容易直接完成實時的消息推送功能,如聊天室、后臺消息提醒、實時更新數(shù)據(jù)等功能,但通過polling、Long polling、長連接、Flash Socket以及HTML5中定義的WebSocket能完成該功能需要。
1.1 Socket簡介
Socket又稱“套接字”,應(yīng)用程序通常通過"套接字"向網(wǎng)絡(luò)發(fā)出請求或者應(yīng)答網(wǎng)絡(luò)請求。Socket的英文原義是“孔”或“插座”,作為UNIX的進程通信機制。Socket可以實現(xiàn)應(yīng)用程序間網(wǎng)絡(luò)通信。
1.2 WebSocket簡介與消息推送
B/S架構(gòu)的系統(tǒng)多使用HTTP協(xié)議,HTTP協(xié)議的特點:
1)無狀態(tài)協(xié)議;
2) 用于通過 Internet 發(fā)送請求消息和響應(yīng)消息;
3) 使用端口接收和發(fā)送消息,默認為80端口。
底層通信還是使用Socket完成。HTTP協(xié)議決定了服務(wù)器與客戶端之間的連接方式,無法直接實現(xiàn)消息推送。
WebSocket是HTML5開始提供的一種瀏覽器與服務(wù)器間進行全雙工通訊的網(wǎng)絡(luò)技術(shù)。依靠這種技術(shù)可以實現(xiàn)客戶端和服務(wù)器端的長連接,雙向?qū)崟r通信。特點:1)事件驅(qū)動;2)異步;3)使用ws或者wss協(xié)議的客戶端socket。
能夠?qū)崿F(xiàn)真正意義上的推送功能。
1.3 WebSocket客戶端
websocket允許通過JavaScript建立與遠程服務(wù)器的連接,從而實現(xiàn)客戶端與服務(wù)器間雙向的通信。在websocket中有兩個方法:
1)send() 向遠程服務(wù)器發(fā)送數(shù)據(jù);2)close() 關(guān)閉該websocket鏈接。
websocket同時還定義了幾個監(jiān)聽函數(shù):
1)onopen 當(dāng)網(wǎng)絡(luò)連接建立時觸發(fā)該事件;2)onerror 當(dāng)網(wǎng)絡(luò)發(fā)生錯誤時觸發(fā)該事件;3)onclose 當(dāng)websocket被關(guān)閉時觸發(fā)該事件;4)onmessage 當(dāng)websocket接收到服務(wù)器發(fā)來的消息的時觸發(fā)的事件,也是通信中最重要的一個監(jiān)聽事件。
websocket的url開頭是ws,如果需要ssl加密可以使用wss,當(dāng)我們調(diào)用websocket的構(gòu)造方法構(gòu)建一個websocket對象(new WebSocket(url))的之后,就可以進行即時通信了。
2 需求分析
以博客系統(tǒng)中評論被回復(fù)為例,當(dāng)一條評論被其他某個用戶(假設(shè)是用戶B)回復(fù),即發(fā)一條通知給被回復(fù)的評論所屬人(假設(shè)是用戶A),告訴A,他的評論被回復(fù)了。
2.1 功能分析:
1)我們不能保證用戶B和用戶A都處于連接狀態(tài),但是通常情況下,用戶B至少是連接狀態(tài),用戶A不一定跟server保持連接;
2)任一用戶都不止對應(yīng)一個客戶端。換言之,用戶A和用戶B都可能打開了多個tab頁,對于一個tab頁,就會有一個獨立的fd標(biāo)識,我們這里認為任一用戶只有最新的fd有效,或者你可以認為用戶所有的tab頁的連接都有效;
3)因為沒有用戶系統(tǒng),我們以get傳遞的參數(shù)uid為標(biāo)識,uid=100視為用戶A,uid=101視為用戶B;
4)我們不準(zhǔn)備做一個評論系統(tǒng),我們模擬的tab頁包將會包含一個輸入內(nèi)容的文本框、一個輸入目標(biāo)uid的input和一個發(fā)送的按鈕以滿足需求。
2.2流程分析:
1)戶A($_GET['uid'] = 100)在某個tab頁的輸入框輸入“回復(fù)xxx的內(nèi)容”字樣后,點擊發(fā)送。
2)戶B($_GET['uid'] = 101)如果處于連接狀態(tài),則alert提醒用戶B,他的評論被回復(fù)了。
3關(guān)鍵代碼實現(xiàn)
將WebSwoole的創(chuàng)建和相關(guān)的回調(diào)封裝到一個類中,服務(wù)器端代碼邏輯如下:
1)我們給CommentServer類增加了一個屬性 $user2fd,這個是key => value結(jié)構(gòu),用于保存uid和fd的映射關(guān)系。
2)onOpen回調(diào)做兩件事,驗證授權(quán)和添加新的映射關(guān)系。
3)onMessage回調(diào)只接收含有event項的數(shù)組,event等同于CommentServer類的方法名,我們這里只有一個用于web通知的alertTip方法。
此外,我們封裝了該類的close方法和push方法,close方法用于server主動關(guān)閉連接,刪除uid和fd的映射,push方法用于向指定的fd推送消息
class CommentServer
{ private $_serv; //webswoole變量
public $key = '^abc&swoole;$';//token的生成鑰匙
// 用戶id和fd對應(yīng)的映射,key => value,key是用戶的uid,value是用戶的fd
public $user2fd = [];
public function __construct()
{ $this->_serv = new swoole_websocket_server("127.0.0.1", 9501); //創(chuàng)建webswoole
$this->_serv->set([
'worker_num' => 1,
'heartbeat_check_interval' => 60,//心跳檢測
'heartbeat_idle_time' => 125,
]);
//綁定回調(diào)
$this->_serv->on('open', [$this, 'onOpen']);
$this->_serv->on('message', [$this, 'onMessage']);
$this->_serv->on('close', [$this, 'onClose']);
}
public function onOpen($serv, $request)
{ // 連接授權(quán)
$accessResult = $this->checkAccess($serv, $request);
if (!$accessResult) {
return false;
}
if (array_key_exists($request->get['uid'], $this->user2fd)) {
始終把用戶最新的fd跟uid映射在一起
return false;
} else {
$this->user2fd[$request->get['uid']] = $request->fd;}}
public function onMessage($serv, $frame)
{// 校驗數(shù)據(jù)的有效性,數(shù)據(jù)被`json_decode`處理之后是數(shù)組并且數(shù)組的`event`項非空才是有效數(shù)據(jù)
// 非有效數(shù)據(jù),關(guān)閉該連接
$data = $frame->data;
$data = json_decode($data, true);
if (非有效數(shù)據(jù)) {關(guān)閉該連接}
// 根據(jù)數(shù)據(jù)的`event`項,判斷要做什么,`event`映射到當(dāng)前類具體的某一個方法,方法不存在則關(guān)閉連接
$method = $data['event'];
if (方法不存在) {關(guān)閉連接}
$this->$method($frame->fd, $data); }//執(zhí)行當(dāng)前類的某個具體方法
//webswoole服務(wù)器端在收到客戶端數(shù)據(jù)后要執(zhí)行的操作
public function alertTip($fd, $data)
{ if (推送目標(biāo)用戶的uid非真或者該uid尚無保存的映射fd) {
關(guān)閉連接
}
//向目標(biāo)用戶推送消息提醒
$this->push(目標(biāo)用戶, ['event' => $data['event'], 'msg' => '收到一條新的回復(fù).']);
}
public function start()
{
$this->_serv->start();
}}
客戶端代碼如下CommentClient.php:
發(fā)送內(nèi)容:
發(fā)送給誰:
var ws = new WebSocket("ws://127.0.0.1:9501?uid=<?php echo $uid ?>&token=<?php echo $token; ?>");
ws.onopen = function(event) {
};
ws.onmessage = function(event) {
var data = event.data;
data = eval("("+data+")");
if (data.event == 'alertTip') { alert(data.msg); }
};
ws.onclose = function(event) { console.log('Client has closed.\n'); };
function send() {
var obj = document.getElementById('content');
var content = obj.value;
var toUid = document.getElementById('toUid').value;
ws.send('{"event":"alertTip", "toUid": '+toUid+'}');}
在瀏覽器中訪問127.0.0.1/commentclient.php?uid=101時,會有兩個輸入框,一個填寫消息推送內(nèi)容,一個填寫目標(biāo)用戶,點擊發(fā)送,則在線的目標(biāo)用戶能馬上收到消息提醒。
4 總結(jié)
該消息推送功能是在沒有用戶系統(tǒng)的情況下實現(xiàn)的,所以只能硬編碼了兩個用戶A和B進行模擬,并且當(dāng)目標(biāo)用戶B不在線時,他是收不到消息提醒的,如果想在目標(biāo)用戶上線后依然能收到消息提醒,可以先把對A的評論通知存儲起來,在用戶A連接的時候在進行通知。
參考文獻:
[1] Matt Zandstra.深入PHP:面向?qū)ο蟆⒛J脚c實踐[M].北京:人民郵電出版社,2011.
[2] Yii Framework中文網(wǎng)文檔中心[EB/OL].https://www.yiichina.com/doc.
[3] 白狼棧[EB/OL]. http://www.manks.top/.
【通聯(lián)編輯:張薇】