第一步:安装thinkpph程序包
第二步:切换到根目录,使用composer require workerman/gateway-worker 安装Linux版本的gateway。(前提是你服务器安装了composer )
http://www.workerman.net/workerman-chat 下载官方实例:workerman-chat 将文件夹内的applicationschatWeb下的文件上传到:publicstaticindex下
第三部:在public目录下:新建server.php
<?php // +---------------------------------------------------------------------- // | ThinkPHP [ WE CAN DO IT JUST THINK ] // +---------------------------------------------------------------------- // | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved. // +---------------------------------------------------------------------- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) // +---------------------------------------------------------------------- // | Author: liu21st <liu21st@gmail.com> // +---------------------------------------------------------------------- // [ 应用入口文件 ] // 定义应用目录 define('APP_PATH', __DIR__ . '/../application/'); define('BIND_MODULE','gateway/Run'); // 加载框架引导文件 require __DIR__ . '/../thinkphp/start.php';
切换到:public目录下执行:php server.php start
第四部:新建gateway模块 下建三个控制器:
(1)Run.php
<?php namespace appgatewaycontroller; use thinkController; use WorkermanWorker; use GatewayWorkerRegister; use GatewayWorkerBusinessWorker; use GatewayWorkerGateway; class Run { public function __construct() { // 初始化register new Register('text://0.0.0.0:1236'); //初始化 bussinessWorker 进程 $worker = new BusinessWorker(); $worker->name = 'RoomBusinessWorker'; $worker->count = 4; $worker->registerAddress = '127.0.0.1:1236'; // 设置处理业务的类,此处制定Events的命名空间 $worker->eventHandler = 'appgatewaycontrollerEvents'; // 初始化 gateway 进程 $gateway = new Gateway("websocket://0.0.0.0:7272"); $gateway->name = 'RoomGateway'; $gateway->count = 4; $gateway->lanIp = '127.0.0.1'; $gateway->startPort = 2900; $gateway->registerAddress = '127.0.0.1:1236'; // 运行所有Worker; Worker::runAll(); } }
(2) Events.php
<?php namespace appgatewaycontroller; use GatewayWorkerLibGateway; use thinkController; class Events extends Controller { /** * 有消息时 * @param integer $client_id 连接的客户端 * @param mixed $message * @return void */ 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." "; // 客户端传递的是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($new_message)); 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:'' "; // 从房间的客户端列表中删除 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)); } } /** * 当用户连接时触发的方法 * @param integer $client_id 连接的客户端 * @return void */ public static function onConnect($client_id) { Gateway::sendToClient($client_id, "Your client_id is $client_id"); } /** * 当用户断开连接时触发的方法 * @param integer $client_id 断开连接的客户端 * @return void */ // public static function onClose($client_id) // { // Gateway::sendToAll("client[$client_id] logout "); // } /** * 当进程启动时 * @param integer $businessWorker 进程实例 */ public static function onWorkerStart($businessWorker) { echo "WorkerStart "; } /** * 当进程关闭时 * @param integer $businessWorker 进程实例 */ public static function onWorkerStop($businessWorker) { echo "WorkerStop "; } }
(3)index.php
<?php namespace appgatewaycontroller; use thinkController; class Index extends Controller { public function index() { return $this->fetch(); } }
第五部:gateway模块下新建视图文件:
<html><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>workerman-chat PHP聊天室 Websocket(HTLM5/Flash)+PHP多进程socket实时推送技术</title> <link href="/static/index/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/index/css/style.css" rel="stylesheet"> <script type="text/javascript" src="/static/index/js/swfobject.js"></script> <script type="text/javascript" src="/static/index/js/web_socket.js"></script> <script type="text/javascript" src="/static/index/js/jquery.min.js"></script> <script type="text/javascript"> if (typeof console == "undefined") { this.console = { log: function (msg) { } };} // 如果浏览器不支持websocket,会使用这个flash自动模拟websocket协议,此过程对开发者透明 WEB_SOCKET_SWF_LOCATION = "/static/index/swf/WebSocketMain.swf"; // 开启flash的websocket debug WEB_SOCKET_DEBUG = true; var ws, name, client_list={}; // 连接服务端 function connect() { // 创建websocket ws = new WebSocket("ws://"+document.domain+":7272"); // 当socket连接打开时,输入用户名 ws.onopen = onopen; // 当有消息时根据消息类型显示不同信息 ws.onmessage = onmessage; ws.onclose = function() { console.log("连接关闭,定时重连"); connect(); }; ws.onerror = function() { console.log("出现错误"); }; } // 连接建立时发送登录信息 function onopen() { if(!name) { show_prompt(); } // 登录 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); } // 服务端发来消息时 function onmessage(e) { console.log(e.data); var data = eval("("+e.data+")"); switch(data['type']){ // 服务端ping客户端 case 'ping': ws.send('{"type":"pong"}'); break;; // 登录 更新用户列表 case 'login': //{"type":"login","client_id":xxx,"client_name":"xxx","client_list":"[...]","time":"xxx"} say(data['client_id'], data['client_name'], data['client_name']+' 加入了聊天室', data['time']); if(data['client_list']) { client_list = data['client_list']; } else { client_list[data['client_id']] = data['client_name']; } flush_client_list(); console.log(data['client_name']+"登录成功"); break; // 发言 case 'say': //{"type":"say","from_client_id":xxx,"to_client_id":"all/client_id","content":"xxx","time":"xxx"} say(data['from_client_id'], data['from_client_name'], data['content'], data['time']); break; // 用户退出 更新用户列表 case 'logout': //{"type":"logout","client_id":xxx,"time":"xxx"} say(data['from_client_id'], data['from_client_name'], data['from_client_name']+' 退出了', data['time']); delete client_list[data['from_client_id']]; flush_client_list(); } } // 输入姓名 function show_prompt(){ name = prompt('输入你的名字:', ''); if(!name || name=='null'){ name = '游客'; } } // 提交对话 function onSubmit() { var input = document.getElementById("textarea"); var to_client_id = $("#client_list option:selected").attr("value"); var to_client_name = $("#client_list option:selected").text(); ws.send('{"type":"say","to_client_id":"'+to_client_id+'","to_client_name":"'+to_client_name+'","content":"'+input.value.replace(/"/g, '\"').replace(/ /g,'\n').replace(/ /g, '\r')+'"}'); input.value = ""; input.focus(); } // 刷新用户列表框 function flush_client_list(){ var userlist_window = $("#userlist"); var client_list_slelect = $("#client_list"); userlist_window.empty(); client_list_slelect.empty(); userlist_window.append('<h4>在线用户</h4><ul>'); client_list_slelect.append('<option value="all" id="cli_all">所有人</option>'); for(var p in client_list){ userlist_window.append('<li id="'+p+'">'+client_list[p]+'</li>'); client_list_slelect.append('<option value="'+p+'">'+client_list[p]+'</option>'); } $("#client_list").val(select_client_id); userlist_window.append('</ul>'); } // 发言 function say(from_client_id, from_client_name, content, time){ $("#dialog").append('<div class="speech_item"><img src="http://lorempixel.com/38/38/?'+from_client_id+'" class="user_icon" /> '+from_client_name+' <br> '+time+'<div style="clear:both;"></div><p class="triangle-isosceles top">'+content+'</p> </div>'); } $(function(){ select_client_id = 'all'; $("#client_list").change(function(){ select_client_id = $("#client_list option:selected").attr("value"); }); }); </script> </head> <body onload="connect();"> <div class="container"> <div class="row clearfix"> <div class="col-md-1 column"> </div> <div class="col-md-6 column"> <div class="thumbnail"> <div class="caption" id="dialog"></div> </div> <form onsubmit="onSubmit(); return false;"> <select style="margin-bottom:8px" id="client_list"> <option value="all">所有人</option> </select> <textarea class="textarea thumbnail" id="textarea"></textarea> <div class="say-btn"><input type="submit" class="btn btn-default" value="发表" /></div> </form> <div> <b>房间列表:</b>(当前在 房间<?php echo isset($_GET['room_id'])&&intval($_GET['room_id'])>0 ? intval($_GET['room_id']):1; ?>)<br> <a href="/?room_id=1">房间1</a> <a href="/?room_id=2">房间2</a> <a href="/?room_id=3">房间3</a> <a href="/?room_id=4">房间4</a> <br><br> </div> <p class="cp">PHP多进程+Websocket(HTML5/Flash)+PHP Socket实时推送技术 Powered by <a href="http://www.workerman.net/workerman-chat" target="_blank">workerman-chat</a></p> </div> <div class="col-md-3 column"> <div class="thumbnail"> <div class="caption" id="userlist"></div> </div> <a href="#" target="_blank"><img style="252px;margin-left:5px;" src="/static/index/img/workerman-todpole.png"></a> </div> </div> </div> </body> </html>
见目录:
最终效果:
记得在linux系统中把使用的端口允许访问