一、安装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_idvar 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_idif (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();}