nginx配置websocket支持wss

在正常業(yè)務(wù)中經(jīng)常會(huì)碰到服務(wù)器需要向客戶端發(fā)起主動(dòng)推送數(shù)據(jù)的需求,其實(shí)有很多實(shí)現(xiàn)方案箍鼓,

①樊销、客戶端輪詢接口
  • 優(yōu)點(diǎn):簡(jiǎn)單
  • 缺點(diǎn):如果是請(qǐng)求頻率比較高,業(yè)務(wù)場(chǎng)景比較常用声诸,可能會(huì)對(duì)服務(wù)器造成比較大的壓力酱讶。
②、借用第三方推送服務(wù)的靜默推送(即消息模式)彼乌,比如友盟泻肯,極光
  • 優(yōu)點(diǎn):對(duì)服務(wù)器壓力小,不需要自建長(zhǎng)連接服務(wù)慰照,移動(dòng)客戶端支持也比較好
  • 缺點(diǎn):依賴于第三方灶挟,受制于人,web版不支持
③毒租、自建長(zhǎng)連接服務(wù)
  • 優(yōu)點(diǎn):提升技能點(diǎn)稚铣,鍛煉能力,可定制化需求墅垮,自由度高
  • 缺點(diǎn):需要開(kāi)發(fā)時(shí)間惕医,配置socket服務(wù),需守護(hù)進(jìn)程保證服務(wù)不中斷

下面我們就開(kāi)始研究如何用PHP實(shí)現(xiàn)長(zhǎng)連接問(wèn)題:
PHP自身支持socket編程算色,但是比較繁瑣抬伺,網(wǎng)上常用的輪子有兩種 swoole (c 擴(kuò)展) 和 workerman(PHPsocket),本文以workerman為例灾梦。

1峡钓、下載workerman包

workerman官網(wǎng)地址:https://www.workerman.net/workerman
支持直接下載,或者composer安裝

2若河、測(cè)試socket連接

首先在把下載的包解壓放在php項(xiàng)目里能岩,在根目錄建立一個(gè)start.php文件

<?php
use Workerman\Worker;
require_once 'Autoloader.php';

// 創(chuàng)建一個(gè)Worker監(jiān)聽(tīng)2346端口,使用websocket協(xié)議通訊
$ws_worker = new Worker("websocket://0.0.0.0:2345");

// 啟動(dòng)4個(gè)進(jìn)程對(duì)外提供服務(wù)
$ws_worker->count = 4;

// 當(dāng)收到客戶端發(fā)來(lái)的數(shù)據(jù)后返回hello $data給客戶端
$ws_worker->onMessage = function($connection, $data)
{
    // 向客戶端發(fā)送hello $data
    $connection->send('hello ' . $data);
};

// 運(yùn)行
Worker::runAll();

然后建立html文件牡肉,index.html

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>測(cè)試websocket</title>
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的瀏覽器支持 WebSocket!");
               
               // 打開(kāi)一個(gè) web socket
               var ws = new WebSocket("ws://127.0.0.1:2345");
                
               ws.onopen = function()
               {
                  // Web Socket 已連接上捧灰,使用 send() 方法發(fā)送數(shù)據(jù)
                  ws.send("發(fā)送數(shù)據(jù)");
                  alert("數(shù)據(jù)發(fā)送中...");
               };
                
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert(received_msg);
               };
                
               ws.onclose = function()
               { 
                  // 關(guān)閉 websocket
                  alert("連接已關(guān)閉..."); 
               };
            }
            
            else
            {
               // 瀏覽器不支持 WebSocket
               alert("您的瀏覽器不支持 WebSocket!");
            }
         }
      </script>
        
   </head>
   <body>
   
      <div id="sse">
         <a href="javascript:WebSocketTest()">運(yùn)行 WebSocket</a>
      </div>
      
   </body>
</html>

然后在cmd命令行窗口進(jìn)入項(xiàng)目目錄,運(yùn)行

php start.php start -d

會(huì)看到


image.png

就代表服務(wù)以及啟動(dòng)成功了
接下里打開(kāi)index.html统锤,運(yùn)行毛俏,如果能正常收到頁(yè)面的alert消息,就代表通訊已經(jīng)沒(méi)有問(wèn)題了饲窿。

3煌寇、正式業(yè)務(wù)中如何使用主動(dòng)推送
上面的例子,我們只是建立好了socket連接逾雄,客戶端在發(fā)送內(nèi)容到服務(wù)器之后能收到返回的消息阀溶,這時(shí)候我們?nèi)绾巫尫?wù)器主動(dòng)給客戶端推送消息呢腻脏。實(shí)現(xiàn)的思想其實(shí)是建立一個(gè)對(duì)外監(jiān)聽(tīng)的worker容器,再開(kāi)啟一個(gè)內(nèi)部數(shù)據(jù)推送監(jiān)聽(tīng)的端口银锻,再把客戶端通過(guò)uid做一個(gè)映射永品,通過(guò)監(jiān)聽(tīng)內(nèi)部端口的數(shù)據(jù),來(lái)實(shí)現(xiàn)把數(shù)據(jù)轉(zhuǎn)發(fā)到對(duì)應(yīng)的映射內(nèi)的客戶端來(lái)實(shí)現(xiàn)击纬。友盟的推送鼎姐,laravel的廣播功能,都是通過(guò)這種邏輯實(shí)現(xiàn)的更振。
下面分別貼一下服務(wù)器端服務(wù)代碼炕桨,服務(wù)器端推送代碼,客戶端html代碼就可以輕松看明白了肯腕。
a献宫、服務(wù)代碼 start.php

<?php
use Workerman\Worker;
require_once 'Autoloader.php';
// 初始化一個(gè)worker容器,監(jiān)聽(tīng)1234端口
global $worker;
$worker = new Worker('websocket://0.0.0.0:1234');
// 這里進(jìn)程數(shù)必須設(shè)置為1
$worker->count = 1;
// worker進(jìn)程啟動(dòng)后建立一個(gè)內(nèi)部通訊端口
$worker->onWorkerStart = function($worker)
{
    // 開(kāi)啟一個(gè)內(nèi)部端口实撒,方便內(nèi)部系統(tǒng)推送數(shù)據(jù)姊途,Text協(xié)議格式 文本+換行符
    $inner_text_worker = new Worker('Text://0.0.0.0:5678');
    $inner_text_worker->onMessage = function($connection, $buffer)
    {
        global $worker;
        // $data數(shù)組格式,里面有uid奈惑,表示向那個(gè)uid的頁(yè)面推送數(shù)據(jù)
        $data = json_decode($buffer, true);
        $uid = $data['uid'];
        // 通過(guò)workerman吭净,向uid的頁(yè)面推送數(shù)據(jù)
        $ret = sendMessageByUid($uid, $buffer);
        // 返回推送結(jié)果
        $connection->send($ret ? 'ok' : 'fail');
    };
    $inner_text_worker->listen();
};
// 新增加一個(gè)屬性,用來(lái)保存uid到connection的映射
$worker->uidConnections = array();
// 當(dāng)有客戶端發(fā)來(lái)消息時(shí)執(zhí)行的回調(diào)函數(shù)
$worker->onMessage = function($connection, $data)use($worker)
{
    // 判斷當(dāng)前客戶端是否已經(jīng)驗(yàn)證,既是否設(shè)置了uid
    if(!isset($connection->uid))
    {
       // 沒(méi)驗(yàn)證的話把第一個(gè)包當(dāng)做uid(這里為了方便演示肴甸,沒(méi)做真正的驗(yàn)證)
       $connection->uid = $data;
       /* 保存uid到connection的映射寂殉,這樣可以方便的通過(guò)uid查找connection,
        * 實(shí)現(xiàn)針對(duì)特定uid推送數(shù)據(jù)
        */
       $worker->uidConnections[$connection->uid] = $connection;
       $connection->send($data);
       return;
    }
};

// 當(dāng)有客戶端連接斷開(kāi)時(shí)
$worker->onClose = function($connection)use($worker)
{
    global $worker;
    if(isset($connection->uid))
    {
        // 連接斷開(kāi)時(shí)刪除映射
        unset($worker->uidConnections[$connection->uid]);
    }
};

// 向所有驗(yàn)證的用戶推送數(shù)據(jù)
function broadcast($message)
{
   global $worker;
   foreach($worker->uidConnections as $connection)
   {
        $connection->send($message);
   }
}

// 針對(duì)uid推送數(shù)據(jù)
function sendMessageByUid($uid, $message)
{
    global $worker;
    if(isset($worker->uidConnections[$uid]))
    {
        $connection = $worker->uidConnections[$uid];
        $connection->send($message);
        return true;
    }
    return false;
}

// 運(yùn)行所有的worker(其實(shí)當(dāng)前只定義了一個(gè))
Worker::runAll();

b原在、推送代碼 push.php

<?php
// 建立socket連接到內(nèi)部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的數(shù)據(jù)友扰,包含uid字段,表示是給這個(gè)uid推送庶柿,這里可以通過(guò)修改uid來(lái)測(cè)試給哪個(gè)客戶端發(fā)推送
$data = array('uid'=>'uid4', 'percent'=>'88%');
// 發(fā)送數(shù)據(jù)村怪,注意5678端口是Text協(xié)議的端口,Text協(xié)議需要在數(shù)據(jù)末尾加上換行符
fwrite($client, json_encode($data)."\n");
// 讀取推送結(jié)果
echo fread($client, 8192);

c浮庐、客戶端HTML index.html

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>uid4的接收頁(yè)面(修改uid即可測(cè)試給哪個(gè)客戶端推送)</title>
      <script type="text/javascript">
         var ws = new WebSocket('ws://127.0.0.1:1234');
            ws.onopen = function(){
                var uid = 'uid4';
                ws.send(uid);
            };
            ws.onmessage = function(e){
                alert(e.data);
            };
      </script>
   </head>
   <body>
      <div id="so">
            測(cè)試頁(yè)面
      </div>
   </body>
</html>

先啟動(dòng)服務(wù)甚负,再打開(kāi)網(wǎng)頁(yè),最后運(yùn)行push.php即可測(cè)試审残,這里比較關(guān)鍵的是進(jìn)程數(shù)必須設(shè)置為1梭域,否則可能無(wú)法推送成功。一個(gè)基礎(chǔ)的長(zhǎng)連接推送就這樣ok了搅轿。
如果需要多進(jìn)程啦病涨、服務(wù)器集群啦、就需要基于Channel組件或者GatewayWorker了璧坟,更多進(jìn)階功能可以參考官方文檔http://doc.workerman.net

wss的nginx服務(wù)器配置

話不多說(shuō)粘貼配置,這個(gè)放在https的配置里面

        location /wss
        {
            proxy_pass http://127.0.0.1:2345;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            rewrite /wss/(.*) /$1 break;
            proxy_redirect off;
        }

接下來(lái)吧前端頁(yè)面的代碼做修改

var ws = new WebSocket("wss://api.pinkechuxing.com/wss");

就是這么簡(jiǎn)單就配置好了

在實(shí)際的使用中我們可能會(huì)遇到連接中斷的情況,這個(gè)時(shí)候就需要發(fā)送心跳包來(lái)維持連接

var ws = new WebSocket("ws://www.goozp.com");

//連接websocket
ws.onopen = function () {
    setInterval(function () {
        ws.send('Hello!');
    }, 10000)
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末既穆,一起剝皮案震驚了整個(gè)濱河市赎懦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幻工,老刑警劉巖励两,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異会钝,居然都是意外死亡伐蒋,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)迁酸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人俭正,你說(shuō)我怎么就攤上這事奸鬓。” “怎么了掸读?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵串远,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我儿惫,道長(zhǎng)澡罚,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任肾请,我火速辦了婚禮留搔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铛铁。我一直安慰自己隔显,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布饵逐。 她就那樣靜靜地躺著括眠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪倍权。 梳的紋絲不亂的頭發(fā)上掷豺,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音薄声,去河邊找鬼当船。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奸柬,可吹牛的內(nèi)容都是我干的生年。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼廓奕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼抱婉!你這毒婦竟也來(lái)了档叔?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蒸绩,失蹤者是張志新(化名)和其女友劉穎衙四,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體患亿,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡传蹈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了步藕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惦界。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖咙冗,靈堂內(nèi)的尸體忽然破棺而出沾歪,到底是詐尸還是另有隱情,我是刑警寧澤雾消,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布灾搏,位于F島的核電站,受9級(jí)特大地震影響立润,放射性物質(zhì)發(fā)生泄漏狂窑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一桑腮、第九天 我趴在偏房一處隱蔽的房頂上張望泉哈。 院中可真熱鬧,春花似錦到旦、人聲如沸旨巷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)采呐。三九已至,卻和暖如春搁骑,著一層夾襖步出監(jiān)牢的瞬間斧吐,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工仲器, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煤率,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓乏冀,卻偏偏與公主長(zhǎng)得像蝶糯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辆沦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355