目录结构 必须按照次目录结构
├── Applications // 这里是所有开发者应用项目 │ └── YourApp // 其中一个项目目录,目录名可以自定义 │ ├── Events.php // 开发者只需要关注这个文件 │ ├── start_gateway.php // gateway进程启动脚本,包括端口号等设置 │ ├── start_businessworker.php // businessWorker进程启动脚本 │ └── start_register.php // 注册服务启动脚本 │ ├── start.php // 全局启动脚本,此脚本会依次加载Applications/项目/start_*.php启动脚本 │ └── vendor // GatewayWorker框架和Workerman框架源码目录,此目录开发者不用关心
一 载入
1.修改composer.json文件
"require": { "php": ">=5.6.4", "laravel/framework": "5.4.*", "laravel/tinker": "~1.0", "predis/predis": "^1.1", "workerman/gateway-worker": "^3.0", "workerman/workerman": "^3.5" },
2. 执行安装命令
composer install 或 composer update 安装成功继续下一步
二 集合到laravel框架
1.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<[email protected]> * @copyright walkor<[email protected]> * @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); /** * 聊天主逻辑 * 主要是处理 onMessage onClose */ use \GatewayWorker\Lib\Gateway; class Events { /** * 有消息时 * @param int $client_id * @param mixed $message */ public static function onMessage($client_id, $message) { // debug echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id session:".json_encode($_SESSION)." onMessage:".$message."\n"; // 客户端传递的是json数据 $message_data = json_decode($message, true); if(!$message_data) { return ; } // 根据类型执行不同的业务 switch($message_data['type']) { // 客户端回应服务端的心跳 case 'pong': return; // 客户端登录 message格式: {type:login, name:xx, room_id:1} ,添加到客户端,广播给所有客户端xx进入聊天室 case 'login': // 判断是否有房间号 if(!isset($message_data['room_id'])) { throw new \Exception("\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message"); } // 把房间号昵称放到session中 $room_id = $message_data['room_id']; $client_name = htmlspecialchars($message_data['client_name']); $_SESSION['room_id'] = $room_id; $_SESSION['client_name'] = $client_name; // 获取房间内所有用户列表 $clients_list = Gateway::getClientSessionsByGroup($room_id); foreach($clients_list as $tmp_client_id=>$item) { $clients_list[$tmp_client_id] = $item['client_name']; } $clients_list[$client_id] = $client_name; // 转播给当前房间的所有客户端,xx进入聊天室 message {type:login, client_id:xx, name:xx} $new_message = array('type'=>$message_data['type'], 'client_id'=>$client_id, 'client_name'=>htmlspecialchars($client_name), 'time'=>date('Y-m-d H:i:s')); Gateway::sendToGroup($room_id, json_encode($new_message)); Gateway::joinGroup($client_id, $room_id); // 给当前用户发送用户列表 $new_message['client_list'] = $clients_list; Gateway::sendToCurrentClient(json_encode(``)); return; // 客户端发言 message: {type:say, to_client_id:xx, content:xx} case 'say': // 非法请求 if(!isset($_SESSION['room_id'])) { throw new \Exception("\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}"); } $room_id = $_SESSION['room_id']; $client_name = $_SESSION['client_name']; // 私聊 if($message_data['to_client_id'] != 'all') { $new_message = array( 'type'=>'say', 'from_client_id'=>$client_id, 'from_client_name' =>$client_name, 'to_client_id'=>$message_data['to_client_id'], 'content'=>"<b>对你说: </b>".nl2br(htmlspecialchars($message_data['content'])), 'time'=>date('Y-m-d H:i:s'), ); Gateway::sendToClient($message_data['to_client_id'], json_encode($new_message)); $new_message['content'] = "<b>你对".htmlspecialchars($message_data['to_client_name'])."说: </b>".nl2br(htmlspecialchars($message_data['content'])); return Gateway::sendToCurrentClient(json_encode($new_message)); } $new_message = array( 'type'=>'say', 'from_client_id'=>$client_id, 'from_client_name' =>$client_name, 'to_client_id'=>'all', 'content'=>nl2br(htmlspecialchars($message_data['content'])), 'time'=>date('Y-m-d H:i:s'), ); return Gateway::sendToGroup($room_id ,json_encode($new_message)); } } /** * 当客户端断开连接时 * @param integer $client_id 客户端id */ public static function onClose($client_id) { // debug echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id onClose:''\n"; // 从房间的客户端列表中删除 if(isset($_SESSION['room_id'])) { $room_id = $_SESSION['room_id']; $new_message = array('type'=>'logout', 'from_client_id'=>$client_id, 'from_client_name'=>$_SESSION['client_name'], 'time'=>date('Y-m-d H:i:s')); Gateway::sendToGroup($room_id, json_encode($new_message)); } } }
2.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<[email protected]> * @copyright walkor<[email protected]> * @link http://www.workerman.net/ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ use \Workerman\Worker; use \GatewayWorker\BusinessWorker; use \Workerman\Autoloader; require_once __DIR__ . '/../../../../../vendor/autoload.php'; // bussinessWorker 进程 $worker = new BusinessWorker(); // worker名称 $worker->name = 'ChatBusinessWorker'; // bussinessWorker进程数量 $worker->count = 4; // 服务注册地址 $worker->registerAddress = '127.0.0.1:1236'; // 如果不是在根目录启动,则运行runAll方法 if(!defined('GLOBAL_START')) { Worker::runAll(); }
3.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<[email protected]> * @copyright walkor<[email protected]> * @link http://www.workerman.net/ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ use \Workerman\Worker; use \GatewayWorker\Gateway; use \Workerman\Autoloader; require_once __DIR__ . '/../../../../../vendor/autoload.php'; // gateway 进程 $gateway = new Gateway("Websocket://0.0.0.0:7272"); // 设置名称,方便status时查看 $gateway->name = 'ChatGateway'; // 设置进程数,gateway进程数建议与cpu核数相同 $gateway->count = 4; // 分布式部署时请设置成内网ip(非127.0.0.1) $gateway->lanIp = '127.0.0.1'; // 内部通讯起始端口。假如$gateway->count=4,起始端口为2300 // 则一般会使用2300 2301 2302 2303 4个端口作为内部通讯端口 $gateway->startPort = 2300; // 心跳间隔 $gateway->pingInterval = 10; // 心跳数据 $gateway->pingData = '{"type":"ping"}'; // 服务注册地址 $gateway->registerAddress = '127.0.0.1:1236'; /* // 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调 $gateway->onConnect = function($connection) { $connection->onWebSocketConnect = function($connection , $http_header) { // 可以在这里判断连接来源是否合法,不合法就关掉连接 // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接 if($_SERVER['HTTP_ORIGIN'] != 'http://chat.workerman.net') { $connection->close(); } // onWebSocketConnect 里面$_GET $_SERVER是可用的 // var_dump($_GET, $_SERVER); }; }; */ // 如果不是在根目录启动,则运行runAll方法 if(!defined('GLOBAL_START')) { Worker::runAll(); }
4.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<[email protected]> * @copyright walkor<[email protected]> * @link http://www.workerman.net/ * @license http://www.opensource.org/licenses/mit-license.php MIT License */ use \Workerman\Worker; use \GatewayWorker\Gateway; use \Workerman\Autoloader; require_once __DIR__ . '/../../../../../vendor/autoload.php'; // gateway 进程 $gateway = new Gateway("Websocket://0.0.0.0:7272"); // 设置名称,方便status时查看 $gateway->name = 'ChatGateway'; // 设置进程数,gateway进程数建议与cpu核数相同 $gateway->count = 4; // 分布式部署时请设置成内网ip(非127.0.0.1) $gateway->lanIp = '127.0.0.1'; // 内部通讯起始端口。假如$gateway->count=4,起始端口为2300 // 则一般会使用2300 2301 2302 2303 4个端口作为内部通讯端口 $gateway->startPort = 2300; // 心跳间隔 $gateway->pingInterval = 10; // 心跳数据 $gateway->pingData = '{"type":"ping"}'; // 服务注册地址 $gateway->registerAddress = '127.0.0.1:1236'; /* // 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调 $gateway->onConnect = function($connection) { $connection->onWebSocketConnect = function($connection , $http_header) { // 可以在这里判断连接来源是否合法,不合法就关掉连接 // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接 if($_SERVER['HTTP_ORIGIN'] != 'http://chat.workerman.net') { $connection->close(); } // onWebSocketConnect 里面$_GET $_SERVER是可用的 // var_dump($_GET, $_SERVER); }; }; */ // 如果不是在根目录启动,则运行runAll方法 if(!defined('GLOBAL_START')) { Worker::runAll(); }
5.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'; //var_dump(glob(__DIR__.'/Chat/*/start*.php'));exit; // 加载所有Applications/*/start.php,以便启动所有服务 foreach(glob(__DIR__.'/app/Http/Controllers/App2/*/start*.php') as $start_file) { //echo $start_file;exit; require_once $start_file; } // 运行所有服务 Worker::runAll();
三 启动
php start.php start
四 前端测试
<script> var ws, name; ws = new WebSocket("ws://think.com:7272"); ws.onopen = onopen; ws.onmessage = function(e){ // json数据转换成js对象 var data = eval("("+e.data+")"); var type = data.type || ''; switch(type){ // Events.php中返回的init类型的消息,将client_id发给后台进行uid绑定 case 'init': case 'login': console.log('登录成功'); break; // 当mvc框架调用GatewayClient发消息时直接alert出来 default : // alert(e.data); } }; function onopen() { if(!name) { name='ddddd'; } // 登录 var login_data = '{"type":"login","client_name":"'+name.replace(/"/g, '\\"')+'","room_id":"<?php echo isset($_GET['room_id']) ? $_GET['room_id'] : 1?>"}'; // console.log("websocket握手成功,发送登录数据:"+login_data); ws.send(login_data); } </script>
走到这一部就差不多啦
剩下的就看你自己的业务逻辑怎么处理咯