tp5+workman(GatewayWorker) 安装及使用

一、安装thinkphp5

1、宝塔删除php禁用函数putenv、pcntl_signal_dispatch、pcntl_wai、pcntl_signal、pcntl_alarm、pcntl_fork,执行安装命令。

composer create-project topthink/think=5.0.* tp5  --prefer-dist

2、配置好站点之后,浏览器打开访问成功。

二、tp5安装GatewayWorker

1、进入tp5目录,安装GatewayWorker

composer require workerman/gateway-worker

如果报错安装指定版本

2、安装workman

composer require workerman/workerman

如果报错安装指定版本

3、安装gatewayclient

composer require workerman/gatewayclient

如果报错安装指定版本

三、使用GatewayWorker  

注:我已修改默认端口号,在宝塔开启端口号

1、创建文件   tp5/public/start.php

<?php
/**
 * run with command
 * php start.php start
 */

ini_set('display_errors', 'on');
use Workerman\Worker;

if(strpos(strtolower(PHP_OS), 'win') === 0)
{
    exit("start.php not support windows, please use start_for_win.bat\n");
}

// 检查扩展
if(!extension_loaded('pcntl'))
{
    exit("Please install pcntl extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
}

if(!extension_loaded('posix'))
{
    exit("Please install posix extension. See http://doc3.workerman.net/appendices/install-extension.html\n");
}

// 标记是全局启动
define('GLOBAL_START', 1);

require_once __DIR__ . '/../vendor/autoload.php';
// 加载所有Applications/*/start.php,以便启动所有服务 application更改为自己文件夹名字,我的为websocket
foreach(glob(__DIR__.'/../api/websocket/start*.php') as $start_file)
{
    require_once $start_file;
}
// 运行所有服务
Worker::runAll();

2、创建文件  tp5/api/Socket/Events.php  (创建php文件,或者下载demo复制过去即可)

<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */

/**
 * 用于检测业务代码死循环或者长时间阻塞等问题
 * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
 * 然后观察一段时间workerman.log看是否有process_timeout异常
 */
//declare(ticks=1);

use \GatewayWorker\Lib\Gateway;

/**
 * 主逻辑
 * 主要是处理 onConnect onMessage onClose 三个方法
 * onConnect 和 onClose 如果不需要可以不用实现并删除
 */
class Events
{
    /**
     * 当客户端连接时触发
     * 如果业务不需此回调可以删除onConnect
     * 
     * @param int $client_id 连接id
     */
    public static function onConnect($client_id)
    {
        echo "【新的客户端链接】:client_id:".$client_id.PHP_EOL;
        // 向当前client_id发送数据 
        Gateway::sendToClient($client_id, "Hello $client_id\r\n");
        // 向所有人发送
        Gateway::sendToAll("$client_id login\r\n");
    }
    
   /**
    * 当客户端发来消息时触发
    * @param int $client_id 连接id
    * @param mixed $message 具体消息
    */
   public static function onMessage($client_id, $message)
   {
        // 向所有人发送 
        Gateway::sendToAll("$client_id said $message\r\n");
   }
   
   /**
    * 当用户断开连接时触发
    * @param int $client_id 连接id
    */
   public static function onClose($client_id)
   {
       // 向所有人发送 
       GateWay::sendToAll("$client_id logout\r\n");
   }
}

3、创建文件tp5/application/Socket/start_businessworker.php

<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */
use Workerman\Worker;
use Workerman\WebServer;
use GatewayWorker\Gateway;
use GatewayWorker\BusinessWorker;
use Workerman\Autoloader;

// 自动加载类
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/Events.php';


// bussinessWorker 进程
$worker = new BusinessWorker();
// worker名称
$worker->name = 'YourAppBusinessWorker';
// bussinessWorker进程数量
$worker->count = 4;
// 服务注册地址
$worker->registerAddress = '127.0.0.1:23222';

//这行代码防止出现报错:Waring: Events::onMessage is not callable
$worker->eventHandler = 'Events';



// 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

4、创建文件tp5/application/Socket/start_gateway.php

<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */
use \Workerman\Worker;
use \Workerman\WebServer;
use \GatewayWorker\Gateway;
use \GatewayWorker\BusinessWorker;
use \Workerman\Autoloader;

// 自动加载类
require_once __DIR__ . '/../../vendor/autoload.php';

// gateway 进程,这里使用Text协议,可以用telnet测试
$gateway = new Gateway("websocket://0.0.0.0:24222");
// gateway名称,status方便查看
$gateway->name = 'moods';
// gateway进程数
$gateway->count = 4;
// 本机ip,分布式部署时使用内网ip
$gateway->lanIp = '127.0.0.1';
// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000
// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口
$gateway->startPort = 2900;
// 服务注册地址
$gateway->registerAddress = '127.0.0.1:23222';

// 心跳间隔
//$gateway->pingInterval = 1;
// 心跳数据
//$gateway->pingData = '{"type":"ping"}';

/*
// 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调
$gateway->onConnect = function($connection)
{
    $connection->onWebSocketConnect = function($connection , $http_header)
    {
        // 可以在这里判断连接来源是否合法,不合法就关掉连接
        // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接
        if($_SERVER['HTTP_ORIGIN'] != 'http://kedou.workerman.net')
        {
            $connection->close();
        }
        // onWebSocketConnect 里面$_GET $_SERVER是可用的
        // var_dump($_GET, $_SERVER);
    };
};
*/

// 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

5、创建文件tp5/application/Socket/start_register.php

<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link http://www.workerman.net/
 * @license http://www.opensource.org/licenses/mit-license.php MIT License
 */
use \Workerman\Worker;
use \GatewayWorker\Register;

// 自动加载类
require_once __DIR__ . '/../../vendor/autoload.php';

// register 必须是text协议
$register = new Register('text://0.0.0.0:23222');

// 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
    Worker::runAll();
}

6、启动websocket程序

1、防火墙打开8282、1236端口,执行下面命令

//运行

php start.php start


//linux运行

php start.php start -d


//停止


php start.php stop


//检测端口是否以被占用
netstat -an | grep 80

//关闭某个进程
sudo kill -9 进程ID

如果修改文件后一定要先停止在运行一下文件,否则不生效

四、使用GatewayWorker发布广播

1、创建文件tp5/application/index/controller/Index.php,执行这个方法就可以向所有人发布广播了。

<?php
namespace app\index\controller;
use GatewayClient\Gateway;
class Index
{
    public function index()
    {
        Gateway::sendToAll(" index发的消息 \r\n");
    }
}

后面逻辑,自己处理即可

测试发信息内容为

 测试地址: http://www.jsons.cn/websocket/

用户1

用户2

下面是我写的一个例子

数据库:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for cmf_chat
-- ----------------------------
DROP TABLE IF EXISTS `cmf_chat`;
CREATE TABLE `cmf_chat`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用于查找聊天记录',
  `operation_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作类型:send_message发送信息,login登录',
  `send_uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送人',
  `send_client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `to_uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '收信息人',
  `to_client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'openid',
  `content_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送信息类型:text文本',
  `content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '发送信息',
  `signature` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '唯一签名',
  `date` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '日期',
  `time` bigint(20) NULL DEFAULT NULL COMMENT '时间',
  `create_time` bigint(20) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` bigint(20) NULL DEFAULT NULL COMMENT '更新时间',
  `delete_time` bigint(20) NULL DEFAULT 0 COMMENT '软删除',
  `me_client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '存入聊天记录' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of cmf_chat
-- ----------------------------

SET FOREIGN_KEY_CHECKS = 1;

1.安装mysql,插架

使用workerman/mysql 扩展

composer require workerman/mysql

2.处理逻辑   Events.php

<?php
/**
 * This file is part of workerman.
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the MIT-LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @author    walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link      http://www.workerman.net/
 * @license   http://www.opensource.org/licenses/mit-license.php MIT License
 */

/**
 * 用于检测业务代码死循环或者长时间阻塞等问题
 * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
 * 然后观察一段时间workerman.log看是否有process_timeout异常
 */

//declare(ticks=1);

use \GatewayWorker\Lib\Gateway;


/**
 * 主逻辑
 * 主要是处理 onConnect onMessage onClose 三个方法
 * onConnect 和 onClose 如果不需要可以不用实现并删除
 */


error_reporting(0);


class Events
{
    /**
     * 新建一个类的静态成员,用来保存数据库实例
     */
    public static $db = null;

    /**
     * 进程启动后初始化数据库连接 两者都可以
     */
    public static function onWorkerStart($worker)
    {
        //使用gateway_worker扩展
        self::$db = new \GatewayWorker\Lib\DbConnection('47.****.188', '3306', 'kf***od_com', 'Gyt***4jb65cD', 'kf***d_com');

        //使用workerman/mysql 扩展
        //        self::$db = new \Workerman\MySQL\Connection('host', 'port', 'user', 'password', 'db_name');
    }

    /**
     * 当客户端连接时触发
     * 如果业务不需此回调可以删除onConnect
     *
     * @param int $client_id 连接id
     */
    public static function onConnect($client_id)
    {
        // 向当前client_id发送数据
        $restult = [
            'operation_type' => 'login',
            'me_client_id'   => $client_id,
            'content_type'   => "text",
            'content'        => "$client_id login success",
            'signature'      => cmf_random_string(100),
            'date'           => date('Y-m-d H:i:s'),
            'time'           => time(),
            'create_time'    => time(),
        ];

        //存入数据库
        self::$db->insert('cmf_chat')->cols($restult)->query();

        //转换为json格式
        $send_message = json_encode($restult, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);


        //给自己发送注册成功
        Gateway::sendToClient($client_id, $send_message);


        // 向所有人发送
        //Gateway::sendToAll("$client_id login\r\n");
    }

    /**
     * 当客户端发来消息时触发
     * @param int   $client_id 连接id
     * @param mixed $message   具体消息
     */
    public static function onMessage($client_id, $message)
    {
        //写个日志文件
        $contents = json_encode($message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
        //写入日志
        $filename = '../api/websocket/log/';
        !is_dir($filename) && mkdir($filename, 0755, true);
        $file_hwnd = fopen($filename . date('Y-m-d') . ".log", "a+");
        fwrite($file_hwnd, "$client_id----$contents" . "\r\n");
        fclose($file_hwnd);


        //数据格式转为数组
        $message = json_decode($message, true);


        // 向指定人发送
        $restult = [
            'operation_type' => 'send_message',
            'send_client_id' => isset($message['send_client_id']) ? $message['send_client_id'] : '',//用户client_id
            'send_uid'       => isset($message['send_uid']) ? $message['send_uid'] : '',//或者用户uid
            'to_client_id'   => isset($message['to_client_id']) ? $message['to_client_id'] : '',
            'to_uid'         => isset($message['to_uid']) ? $message['to_uid'] : '',
            'openid'         => isset($message['openid']) ? $message['openid'] : '',
            'content_type'   => "text",
            'content'        => $message['content'],
            'signature'      => cmf_random_string(100),
            'date'           => date('Y-m-d H:i:s'),
            'time'           => time(),
            'create_time'    => time(),
        ];

        //存入数据库
        self::$db->insert('cmf_chat')->cols($restult)->query();


        //转换为json格式
        $send_message = json_encode($restult, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);


        //给指定人发信息
        if ($restult['to_client_id']) Gateway::sendToClient($restult['to_client_id'], $send_message);
        if ($restult['to_uid']) Gateway::sendToUid($restult['to_uid'], $send_message);


        //在给自己信息同步一下
        if ($restult['send_client_id']) Gateway::sendToClient($restult['send_client_id'], $send_message);
        if ($restult['send_uid']) Gateway::sendToUid($restult['send_uid'], $send_message);


        //向所有人发送
        //Gateway::sendToAll("$send_message content\r\n");
    }

    /**
     * 当用户断开连接时触发
     * @param int $client_id 连接id
     */
    public static function onClose($client_id)
    {
        // 向所有人发送
        //GateWay::sendToAll("$client_id logout\r\n");
    }


    /**
     * 发送请求,将数据存入数据库中
     * @param $url
     * @param $data
     */
    public function add_chat($url, $data)
    {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = curl_exec($ch);
        //        if ($response === false) {
        //            echo 'Curl error: ' . curl_error($ch);
        //        } else {
        //            echo 'Response: ' . $response;
        //        }

        curl_close($ch);
    }

}

3.前端例子

<!DOCTYPE html>
<html>
	<head>
		<title>WebSocket Example</title>
		<script>
			// 创建WebSocket连接  
			var socket = new WebSocket("ws://47.94.223.188:24222");

			// 连接打开时触发的事件处理函数  
			socket.onopen = function(event) {
				console.log("连接已建立");
			};

			// 发送消息到服务器  
			function sendMessage() {
				var messageInput = document.getElementById("message");
				var message = messageInput.value;


				//获取自己的client_id
				var me_client_id = document.getElementById("me_client_id").value;


				//发送信息 
				var send_message = {
					'operation_type': 'send_message',
					'send_client_id': me_client_id,
					//'send_uid': me_client_id,
					'to_uid': 'w001', 
					'openid': 1,
					'content_type': 'text',
					'content': message,
				}
				const jsonString = JSON.stringify(send_message);
	  
			 



				socket.send(jsonString);
				// messageInput.value = "";
			}

			// 接收到消息时触发的事件处理函数  
			socket.onmessage = function(event) {
				var messageContainer = document.getElementById("message-container");
				var messageElement = document.createElement("p");

 
				//返回数据&处理成数组格式
				var result = event.data;
				var jsonObject = JSON.parse(result);
				//如果类型为 operation_type==login 存一下me_client_id
				if (jsonObject['operation_type'] == 'login') {
					var me_client_id = document.getElementById("me_client_id");
					me_client_id.value = jsonObject.me_client_id;
				}


				messageElement.textContent = event.data;
				messageContainer.appendChild(messageElement);
			};

			// 连接关闭时触发的事件处理函数  
			socket.onclose = function(event) {
				console.log("连接已关闭");
			};

			// 连接发生错误时触发的事件处理函数  
			socket.onerror = function(error) {
				console.error("WebSocket 错误: " + error);
			};
		</script>
	</head>
	<body>
		<h1>WebSocket Example</h1>
		<input type="text" id="me_client_id">
		<input type="text" id="message">
		<button onclick="sendMessage()">发送</button>
		<div id="message-container"></div>
	</body>
</html>

4.拿到client_id 绑定uid

    /**
     * 获取所有在线人数
     * @return array
     * https://kf1***om/api/wxapp/send/get_all_uid_list
     */
    public function get_all_uid_list()
    {
        $restult = Gateway::getAllUidList();
        dump($restult);
        exit();
    }

    /**
     * client_id与uid绑定
     * 传入自己的client_id和openid,自动绑定为w+用户id  例如w1,w2,w100005
     * @return array
     * https://kf****om/api/wxapp/send/bind_uid
     */
    public function bind_uid()
    {
        $client_id = '7f0000010b5400000004';
        $uid       = 'w002';
        Gateway::bindUid($client_id, $uid);
        dump(cmf_random_string(100, 3));
        exit();
    }