WebSocket 是 HTML5 提供的一種網(wǎng)絡(luò)通訊協(xié)議,用于服務(wù)端與客戶端實(shí)時(shí)數(shù)據(jù)傳輸。廣泛用于瀏覽器與服務(wù)器的實(shí)時(shí)通訊,APP與服務(wù)器的實(shí)時(shí)通訊等場景。
相比傳統(tǒng)HTTP協(xié)議請求響應(yīng)式通訊,WebSocket協(xié)議可以做到實(shí)時(shí)的雙向通訊,服務(wù)端可以在任何時(shí)候向客戶端推送數(shù)據(jù)(HTTP協(xié)議需要客戶端發(fā)起請求后才能推送)。
PHP作為世界上最好的語言,自然支持WebSocket協(xié)議。以下是PHP使用WebSocket協(xié)議教程。
教程里使用workerman作為應(yīng)用容器,workerman具備非常高的性能,它不僅支持WebSocket協(xié)議,也支持HTTP協(xié)議、Text協(xié)議、Frame協(xié)議以及其它自定義協(xié)議等。
想象一下我們年會上需要一個(gè)大屏,顯示每一個(gè)公司成員對公司的祝福語。接下來我們就用workerman+WebSocket來實(shí)現(xiàn)它。
首先我們需要整理下它的數(shù)據(jù)流轉(zhuǎn)圖。
員工(手機(jī)瀏覽器) <-------websocket------>[服務(wù)器]<------websocket------>大屏(電腦瀏覽器投屏)
原理比較簡單,手機(jī)瀏覽器和電腦瀏覽器分別與服務(wù)器建立一個(gè)WebSocket連接。手機(jī)瀏覽器通過websocket發(fā)送文字祝福給服務(wù)器,服務(wù)器將文字祝福通過websocket推送給電腦瀏覽器并顯示。
新建目錄 php-websocket
,然后進(jìn)入到 php-websocket
目錄中
composer require workerman/workerman
<?php
require __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
// 使用websocket協(xié)議監(jiān)聽6161端口
$worker = new Worker('websocket://0.0.0.0:6161');
// 當(dāng)瀏覽器(包括用戶手機(jī)瀏覽器和電腦瀏覽器)發(fā)來消息時(shí)的處理邏輯
$worker->onMessage = function(TcpConnection $connection, $data) {
// 這個(gè)靜態(tài)變量用來存儲電腦瀏覽器的websocket連接,方便推送使用
static $daping_connection = null;
switch ($data) {
// 發(fā)送 daping 字符串的是電腦瀏覽器,將其連接保存到靜態(tài)變量中
case 'daping':
$daping_connection = $connection;
break;
// ping 是心跳數(shù)據(jù),用來維持連接,只返回 pong 字符串,無需做其它處理
case 'ping':
$connection->send('pong');
break;
// 用戶手機(jī)瀏覽器發(fā)來的祝福語
default:
// 直接使用電腦瀏覽器的連接將祝福語推送給電腦
if ($daping_connection) {
$daping_connection->send($data);
}
}
};
Worker::runAll();
我們看到服務(wù)端代碼很簡潔,電腦瀏覽器發(fā)起websocket連接后會發(fā)送一個(gè)字符串daping
,告訴服務(wù)端我是電腦瀏覽器,服務(wù)端將這個(gè)連接保存到靜態(tài)變量,方便給它推送數(shù)據(jù)。手機(jī)瀏覽器發(fā)送的數(shù)據(jù)會直接用靜態(tài)變量保存的電腦瀏覽器連接推送過去。
我們注意到有一個(gè)心跳數(shù)據(jù)ping
pong
的交互,這是由于外網(wǎng)環(huán)境很復(fù)雜,連接如果長時(shí)間不通訊(超過1分鐘)連接就會被路由節(jié)點(diǎn)、防火墻等斷開,所以客戶端與服務(wù)端需要在1分鐘內(nèi)至少通訊一次,避免連接斷開,這個(gè)就是心跳的作用。
服務(wù)端開發(fā)完畢,接下來是客戶端。
新建 daping.html
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="jquery.min.js"></script>
<title>WebSocket大屏</title>
</head>
<body>
<ul id="content">
</ul>
</body>
<script>
function connect() {
// 與服務(wù)端建立WebSocket連接
//(為了方便測試這里ip使用的是127.0.0.1,正式環(huán)境請使用外網(wǎng)ip)
ws = new WebSocket('ws://127.0.0.1:6161');
// 連接建立后發(fā)送daping,表明自己是電腦瀏覽器
ws.onopen = function() {
ws.send('daping');
};
// 收到服務(wù)端推送的數(shù)據(jù)后,將數(shù)據(jù)顯示在瀏覽器里(心跳數(shù)據(jù)pong除外)
ws.onmessage = function (e) {
if (e.data !== 'pong') {
$($('#content')).append('<li>'+e.data+'</li>');
}
};
// 沒隔50秒發(fā)送一個(gè)心跳數(shù)據(jù) ping 給服務(wù)器,保持連接
ws.timer = setInterval(function () {
ws.send('ping');
}, 50000);
// 當(dāng)連接關(guān)閉時(shí)清除定時(shí)器,并設(shè)置1秒后重連
ws.onclose = function () {
clearTimeout(ws.timer);
setTimeout(connect, 1000);
};
}
// 執(zhí)行連接
connect();
</script>
</html>
雖然我們做了心跳保持連接,但是仍然無法保證連接不被斷開,比如用戶將瀏覽器切到后臺、網(wǎng)絡(luò)信號差、服務(wù)端重啟等。所以斷線重連是長連接應(yīng)用必備的功能。所以我們需要在客戶端監(jiān)聽連接斷開事件 ws.onclose
,在這里執(zhí)行一個(gè)定時(shí)器執(zhí)行重連。
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>WebSocket大屏</title>
<script src="jquery.min.js"></script>
</head>
<body>
<input type="text" id="content">
<input type="button" value="發(fā)送" onclick="send()">
<script>
function connect() {
ws = new WebSocket('ws://127.0.0.1:6161');
ws.onmessage = function (e) {
console.log(e.data);
};
ws.timer = setInterval(function () {
ws.send('ping');
}, 50000);
ws.onclose = function () {
clearTimeout(ws.timer);
setTimeout(connect, 1000);
};
}
// 通過WebSocket連接將數(shù)據(jù)發(fā)送給服務(wù)端
function send() {
ws.send($('#content').val());
$('#content').val('');
}
connect();
</script>
</body>
</html>
用戶手機(jī)瀏覽器端和電腦瀏覽器端代碼類似。多個(gè)一個(gè)send函數(shù),用來將數(shù)據(jù)發(fā)送給服務(wù)端。
html代碼里使用了jquery,請自行下載放置到本地。
終端運(yùn)行 php start.php start -d
,啟動workerman的websocket服務(wù)。
終端運(yùn)行 php -S 0.0.0.0:7171
,這樣利用php cli
啟動了一個(gè)webserver監(jiān)聽7171端口。
瀏覽器訪問 http://127.0.0.1:7171/daping.html
和 http://127.0.0.1:7171/user.html
這樣在user.html
發(fā)送的文字會展示在 daping.html
上
如果頁面訪問超時(shí),請?jiān)诎踩M或者防火墻沒有放行6161 7171端口端口
以上是PHP實(shí)現(xiàn)WebSocket的一個(gè)教程,主要講解workerman的websocket協(xié)議用法。有些細(xì)節(jié)沒有做處理,比如用戶鑒權(quán),傳遞用戶昵稱頭像等。
http://www.wtbis.cn/doc/workerman/components/channel-examples.html
http://www.wtbis.cn/doc/workerman/components/channel-examples2.html
http://www.wtbis.cn/q/508
http://www.wtbis.cn/web-sender
http://www.wtbis.cn/doc/gateway-worker/push-in-other-project.html
如果你想用PHP編寫復(fù)雜的WebSocket即時(shí)通訊,可以使用GatewayWorker,它是基于workerman開發(fā)的一個(gè)分布式即時(shí)通訊框架,包含眾多常用的API接口。
更多workerman相關(guān)請參考workerman手冊