laravel中使用workman进行长连接

workman官方文档http://doc3.workerman.net/640361

1.将推送的端口号在服务器开放,阿里云在安全组规则里进行添加

2.本人使用的是宝塔,用宝塔的小伙伴还需要再宝塔面板的安全中,放行端口号

3.php.ini中的extension=php_sockets.dll 扩展打开,(去掉前面的;即可)

4.查看phpinfo中的socket是否是enable

以上这些确定之后就可以正式开始开发socket了~

服务端

安装workman

  composer require workerman/workerman

执行php artisan

   执行php artisan make:command orderSocketCommand 在app\Console\Commands生成orderSocketCommand .php文件

    <?php

     namespace App\Console\Commands;

    use Illuminate\Console\Command;

    use Workerman\Worker;

    use Workerman\Lib\Timer;

    use Log;

    class orderSocketCommand extends Command

    {

        protected $signature = 'orderSocketCommand {action} {-d?}';#若果加上该选项,则以守护进程运行

        protected $description = 'Start a orderSocketCommand server';

        protected $heartbeatTime = 60;#消息间隔

        public function __construct() {

            parent::__construct();

        }

        public function handle() {

            global $argv;

            global $worker;

            // argv[0] 默认是当前文件,可以不修改

            $argv[1] = $argv[2];

            $argv[2] = isset($argv[3]) ? "-{$argv[3]}" : '';

            // 创建一个Worker监听6666端口,不使用任何应用层协议

            $worker = new Worker("websocket://0.0.0.0:6666");

            $arg = $this->argument('action');

            switch ($arg) {

                case 'start':

                    $this->start($worker);

                    break;

                case 'stop':

                    $this->stop($worker);

                    break;

            }

        }

        private function start($worker) {

            // 启动1个进程对外提供服务

            $worker->count = 1;

            // worker进程启动后建立一个内部通讯端口

            $worker->onWorkerStart = function($worker){

                // 每10秒执行一次------需要加心跳检测的打开这个注释,并修改自己的逻辑

                // $timeInterval = 10;

                // Timer::add($timeInterval, function() use ($worker) {

                //     $timeNow = time();

                //     foreach ($worker->connections as $connection) {

                //         // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间

                //         if (empty($connection->lastMessageTime)) {

                //             $connection->lastMessageTime = $timeNow;

                //         }

                //         // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接

                //         if ($timeNow - $connection->lastMessageTime > $this->heartbeatTime) {

                //             // 检测客户端是否断开,客户端是否回应,未回应断开

                //             $connection->close();

                //         }

                //     }

                // });

                // 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符

                $inner_text_worker = new Worker('text://0.0.0.0:8888');

                $inner_text_worker->onMessage = function($connection, $buffer)

                {

                    global $worker;

                    // $data数组格式,里面有orderType,表示向那个orderType的页面推送数据

                    $data = json_decode($buffer, true);

                    $orderType = $data['orderType'];

                    // 通过workerman,向orderType的页面推送数据

                    $result = $this->sendMessageByorderType($orderType, $buffer);

                    // 返回给客户端推送结果

                    $connection->send($result? 'success' : 'fail');

                };

                $inner_text_worker->listen();

            };

            // 新增加一个属性,用来保存orderType到connection的映射

            $worker->orderTypeConnections = array();

            // 当有客户端发来消息时执行的回调函数

            $worker->onMessage = function($connection, $data)use($worker) {

                // 判断当前客户端是否已经验证,既是否设置了orderType

                if(!isset($connection->orderType)) {

                    Log::info("Command orderSocketCommand 当有客户端发来orderType消息时执行的回调函数中的send data:".$data);

                    $data = json_decode($data,true);

                    $type = $data['orderType'];

                    $connection->orderType =  $data['uid'] .'-'. $data['orderType'];

                    // 保存orderType到connection的映射,这样可以方便的通过orderType查找connection,实现针对特定orderType推送数据

                    $worker->orderTypeConnections[$type][$connection->orderType] = $connection;

                    $connection->send(json_encode($data));

                    return;

                }else{

                    Log::info("Command orderSocketCommand 客户端发来的所有消息:".json_encode($data));

                    $connection->send($data);

                    return;

                }

            };

            Worker::runAll();

        }

        // 针对orderType推送数据

        private function sendMessageByorderType($orderType, $message) {

            global $worker;

            foreach($worker->orderTypeConnections[$orderType] as $connection) {

                $connection->send($message);

                Log::info("Command orderSocketCommand 一个内部端口,方便内部系统推送数据send data:".json_encode($message));

           }

           return true;

        }

        private function stop($worker) {

            $worker->reloadable = false;

            $worker->onClose = function($connection) {

                echo "orderSocketCommand stop";

            };

            // 运行worker

            Worker::runAll();

        }

    }

Kernel.php中添加

     protected $commands = [

                ......

                Commands\orderSocketCommand::class

            ];

 运行

        php artisan workerman start d

        

        Workerman[artisan] start in DEBUG mode

        ----------------------- WORKERMAN -----------------------------

        Workerman version:3.5.22          PHP version:7.1.13

        ------------------------ WORKERS -------------------------------

        user          worker        listen                          processes status

        root          none          websocket://0.0.0.0:666   1         [OK] 

        ----------------------------------------------------------------

        Start success

这样服务端就写好了,官方文档要好好看真的很有用

其中connection的映射这一块一定要仔细看官网的例子:

    WorkerMan中如何向某个特定客户端发送数据http://doc3.workerman.net/315238

客户端

 将需要的数据信息推送到服务端(我写的数据逻辑之后,可以公共调用)

 <?php

        namespace App\Models;

        use Log;

        class Common{

            public static function push($data){

            // 建立socket连接到内部推送端口

            $client = stream_socket_client('tcp://127.0.0.1:8888', $errno, $errmsg, 1);//本地测试

            if (!$client) {

                            throw new \Exception("{$errstr} ({$errno})", 1);

             }

            // 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符

            fwrite($client, json_encode($data)."\n");

            // 读取推送结果

            $result = fread($client, 4096);

            fclose($client);

            return $result;

        }

   }

客户端接收数据

     var ws = null;

    connect();

    function connect() {

        // 创建一个 websocket 连接

        ws = new WebSocket("ws://127.0.0.1:6666");//本地测试

        // websocket 创建成功事件

        ws.onopen = onopen;

        // websocket 接收到消息事件

        ws.onmessage = onmessage;

        ws.onclose = onclose;

        ws.onerror = onerror;

    }

     function onopen(){

          var orderType = document.querySelector("input#type").value;//这个用来区分socket的不同数据

          var uid = document.querySelector("input#uid").value;

            var data = {'orderType':orderType,'uid':uid}

            ws.send(JSON.stringify(data));

            heartCheck.reset().start();      //心跳检测重置

    }

     // 接受服务端数据是触发事件

    function onmessage(e) {

      heartCheck.reset().start();      //拿到任何消息都说明当前连接是正常的

      // socket推送的数据属于string类型,将其转换成json格式

        var data = e.data;

        data = JSON.parse(data);

        if (data.hasOwnProperty('uid')) {

          console.log('不是所需要的数据')

        }else {

              if (typeof(data) != 'number') {//心跳检测的数据是999,这是不需要的数据

              var _token=$('meta[name=token]').attr('content');

              $.ajax({

                    type: "post",

                    url: "/sys/order/deal_data",

                    data: {'_token':_token,'type':data.orderType},

                    dataType: 'JSON',

                    success: function(json) {

                          var html = ''

                          for (var i = 0; i < json.data.length; i++) {

                            html += ``

                            if (json.data[i].users == null) {

                              html += ``

                            }else{

                              html += `` + json.data[i].users['username'] + `

                            }

                            html += `` + json.data[i].number + ``

                    }

                    var content = document.querySelector('#content');

                    content.innerHTML = html

                    // 自动播放铃声

                    var mp3 = "/music/test.mp3";

                    var mp3 = new Audio(mp3);

                    mp3.play(); //播放音乐提醒有新订单

                }

            });

         }

     }

}

function onclose(){}

function onerror(){}

 //心跳检测

var heartCheck = {

    timeout: 300000,        //五分钟发一次心跳

    timeoutObj: null,

    serverTimeoutObj: null,

    reset: function(){

    clearTimeout(this.timeoutObj);

    clearTimeout(this.serverTimeoutObj);

    return this;

},

start: function(){

    var self = this;

    this.timeoutObj = setTimeout(function(){

    //这里发送一个心跳,后端收到后,返回一个心跳消息,

    //onmessage拿到返回的心跳就说明连接正常

    ws.send("9999");

    self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了

    ws.close();     //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次

    }, self.timeout)

    }, this.timeout)

}

}

这样客户端接收到数据的时候就会播放指定提示音了~

客户端的心跳检测参考:https://www.jianshu.com/p/1141dcf6de3e

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

推荐阅读更多精彩内容

  • 由于自己写了一个博客,一直对workman有想法的我,突发奇想的想在博客里面弄个聊天室,因此就研究了一下workm...
    youngxs阅读 4,091评论 0 0
  • 我的博客文章网址: http://www.jloongking.cn/tp50/public/blog/index...
    非甲即丁阅读 6,532评论 0 4
  • _________________________________________________________...
    fastwe阅读 588评论 0 0
  • Web存储API 对于任何程序来说,能够实现数据存储是必备功能之一,并且在需要的时候能够提供数据。但在过去的Web...
    云音流阅读 901评论 0 1
  • Workerman Manual Workerman 不依赖于 php-fpm、apache、nginx 容器,这...
    xiaojianxu阅读 917评论 1 1