nginx配置websocket支持wss

在正常业务中经?;崤龅椒衿餍枰蚩突Ф朔⑵鹬鞫扑褪莸男枨?,其实有很多实现方案,

①、客户端轮询接口
  • 优点:简单
  • 缺点:如果是请求频率比较高,业务场景比较常用,可能会对服务器造成比较大的压力。
②、借用第三方推送服务的静默推送(即消息模式),比如友盟,极光
  • 优点:对服务器压力小,不需要自建长连接服务,移动客户端支持也比较好
  • 缺点:依赖于第三方,受制于人,web版不支持
③、自建长连接服务
  • 优点:提升技能点,锻炼能力,可定制化需求,自由度高
  • 缺点:需要开发时间,配置socket服务,需守护进程保证服务不中断

下面我们就开始研究如何用PHP实现长连接问题:
PHP自身支持socket编程,但是比较繁琐,网上常用的轮子有两种 swoole (c 扩展) 和 workerman(PHPsocket),本文以workerman为例。

1、下载workerman包

workerman官网地址:https://www.workerman.net/workerman
支持直接下载,或者composer安装

2、测试socket连接

首先在把下载的包解压放在php项目里,在根目录建立一个start.php文件

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

// 创建一个Worker监听2346端口,使用websocket协议通讯
$ws_worker = new Worker("websocket://0.0.0.0:2345");

// 启动4个进程对外提供服务
$ws_worker->count = 4;

// 当收到客户端发来的数据后返回hello $data给客户端
$ws_worker->onMessage = function($connection, $data)
{
    // 向客户端发送hello $data
    $connection->send('hello ' . $data);
};

// 运行
Worker::runAll();

然后建立html文件,index.html

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>测试websocket</title>
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
               
               // 打开一个 web socket
               var ws = new WebSocket("ws://127.0.0.1:2345");
                
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("发送数据");
                  alert("数据发送中...");
               };
                
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert(received_msg);
               };
                
               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            
            else
            {
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         }
      </script>
        
   </head>
   <body>
   
      <div id="sse">
         <a href="javascript:WebSocketTest()">运行 WebSocket</a>
      </div>
      
   </body>
</html>

然后在cmd命令行窗口进入项目目录,运行

php start.php start -d

会看到


image.png

就代表服务以及启动成功了
接下里打开index.html,运行,如果能正常收到页面的alert消息,就代表通讯已经没有问题了。

3、正式业务中如何使用主动推送
上面的例子,我们只是建立好了socket连接,客户端在发送内容到服务器之后能收到返回的消息,这时候我们如何让服务器主动给客户端推送消息呢。实现的思想其实是建立一个对外监听的worker容器,再开启一个内部数据推送监听的端口,再把客户端通过uid做一个映射,通过监听内部端口的数据,来实现把数据转发到对应的映射内的客户端来实现。友盟的推送,laravel的广播功能,都是通过这种逻辑实现的。
下面分别贴一下服务器端服务代码,服务器端推送代码,客户端html代码就可以轻松看明白了。
a、服务代码 start.php

<?php
use Workerman\Worker;
require_once 'Autoloader.php';
// 初始化一个worker容器,监听1234端口
global $worker;
$worker = new Worker('websocket://0.0.0.0:1234');
// 这里进程数必须设置为1
$worker->count = 1;
// worker进程启动后建立一个内部通讯端口
$worker->onWorkerStart = function($worker)
{
    // 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
    $inner_text_worker = new Worker('Text://0.0.0.0:5678');
    $inner_text_worker->onMessage = function($connection, $buffer)
    {
        global $worker;
        // $data数组格式,里面有uid,表示向那个uid的页面推送数据
        $data = json_decode($buffer, true);
        $uid = $data['uid'];
        // 通过workerman,向uid的页面推送数据
        $ret = sendMessageByUid($uid, $buffer);
        // 返回推送结果
        $connection->send($ret ? 'ok' : 'fail');
    };
    $inner_text_worker->listen();
};
// 新增加一个属性,用来保存uid到connection的映射
$worker->uidConnections = array();
// 当有客户端发来消息时执行的回调函数
$worker->onMessage = function($connection, $data)use($worker)
{
    // 判断当前客户端是否已经验证,既是否设置了uid
    if(!isset($connection->uid))
    {
       // 没验证的话把第一个包当做uid(这里为了方便演示,没做真正的验证)
       $connection->uid = $data;
       /* 保存uid到connection的映射,这样可以方便的通过uid查找connection,
        * 实现针对特定uid推送数据
        */
       $worker->uidConnections[$connection->uid] = $connection;
       $connection->send($data);
       return;
    }
};

// 当有客户端连接断开时
$worker->onClose = function($connection)use($worker)
{
    global $worker;
    if(isset($connection->uid))
    {
        // 连接断开时删除映射
        unset($worker->uidConnections[$connection->uid]);
    }
};

// 向所有验证的用户推送数据
function broadcast($message)
{
   global $worker;
   foreach($worker->uidConnections as $connection)
   {
        $connection->send($message);
   }
}

// 针对uid推送数据
function sendMessageByUid($uid, $message)
{
    global $worker;
    if(isset($worker->uidConnections[$uid]))
    {
        $connection = $worker->uidConnections[$uid];
        $connection->send($message);
        return true;
    }
    return false;
}

// 运行所有的worker(其实当前只定义了一个)
Worker::runAll();

b、推送代码 push.php

<?php
// 建立socket连接到内部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的数据,包含uid字段,表示是给这个uid推送,这里可以通过修改uid来测试给哪个客户端发推送
$data = array('uid'=>'uid4', 'percent'=>'88%');
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data)."\n");
// 读取推送结果
echo fread($client, 8192);

c、客户端HTML index.html

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>uid4的接收页面(修改uid即可测试给哪个客户端推送)</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">
            测试页面
      </div>
   </body>
</html>

先启动服务,再打开网页,最后运行push.php即可测试,这里比较关键的是进程数必须设置为1,否则可能无法推送成功。一个基础的长连接推送就这样ok了。
如果需要多进程啦、服务器集群啦、就需要基于Channel组件或者GatewayWorker了,更多进阶功能可以参考官方文档http://doc.workerman.net

wss的nginx服务器配置

话不多说粘贴配置,这个放在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;
        }

接下来吧前端页面的代码做修改

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

就是这么简单就配置好了

在实际的使用中我们可能会遇到连接中断的情况,这个时候就需要发送心跳包来维持连接

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

//连接websocket
ws.onopen = function () {
    setInterval(function () {
        ws.send('Hello!');
    }, 10000)
};
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351