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文件

    

     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

客户端

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

 

        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

你可能感兴趣的:(laravel中使用workman进行长连接)