thinkphp5+GatewayWorker+Workerman
项目地址 ttps://www.workerman.net/workerman-chat
thinkphp5+GatewayWorker+Workerman聊天室,可以多人聊天,指定某个人进行聊天,还可以切换聊天房间
Windows版安装
a) 安装thinkphp5:
composer create-project topthink/think tp5 --prefer-dist
b) 进入tp5的目录,安装Windows版本的workerman:
composer require workerman/workerman-for-win
c)安装Windows版本的gateway:
composer require workerman/gateway-worker-for-win
开始关键部分,服务端实现
控制器 控制器:app\index\controller\Sregister
<?php
namespace app\index\controller;
use Workerman\Worker;
use GatewayWorker\Register;
class Sregister{
public function __construct(){
// register 服务必须是text协议
$register = new Register('text://0.0.0.0:1236'); // 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}
}
}
控制器:app\index\controller\Sgateway
<?php
namespace app\index\controller;
use Workerman\Worker;
use GatewayWorker\Gateway;
use Workerman\Autoloader;
class Sgateway{
public function __construct(){
// gateway 进程
$gateway = new Gateway("Websocket://0.0.0.0:7272");
// 设置名称,方便status时查看
$gateway->name = 'ChatGateway';
// 设置进程数,gateway进程数建议与cpu核数相同
$gateway->count = ;
// 分布式部署时请设置成内网ip(非127.0.0.1)
$gateway->lanIp = '127.0.0.1';
// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000
// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口
$gateway->startPort = ;
// 心跳间隔
$gateway->pingInterval = ;
// 心跳数据
$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();
} }
}
控制器:app\index\controller\Sbusinessworker
<?php
namespace app\index\controller;
use Workerman\Worker;
use GatewayWorker\BusinessWorker;
use Workerman\Autoloader;
class Sbusinessworker{
public function __construct(){
// bussinessWorker 进程
$worker = new BusinessWorker();
// worker名称
$worker->name = 'ChatBusinessWorker';
// bussinessWorker进程数量
$worker->count = ;
// 服务注册地址
$worker->registerAddress = '127.0.0.1:1236';
//设置处理业务的类,此处制定Events的命名空间
$worker->eventHandler = 'app\index\controller\Events'; // 如果不是在根目录启动,则运行runAll方法
if(!defined('GLOBAL_START'))
{
Worker::runAll();
}
}
}
控制器:app\index\controller\Events
<?php
namespace app\index\controller;
/**
* 用于检测业务代码死循环或者长时间阻塞等问题
* 如果发现业务卡死,可以将下面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($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:''\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));
}
} }
代码目录截图
然后在项目根目录 新增入口文件 start_register.php 、start_gateway.php 、start_businessworker.php三个入口文件
文件:start_register.php
<?php
define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','index/Sregister');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';
文件:start_gateway.php
<?php
define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','index/Sgateway');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';
文件:start_businessworker.php
<?php
define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','index/Sbusinessworker');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';
d): 由于PHP-CLI在windows系统无法实现多进程以及守护进程,所以只能把三个文件放到bat文件,然后双击启动
bat文件:start_for_win.bat
php start_register.php start_gateway.php start_businessworker.phppause
代码目录截图
启动程序
在项目的根目录下双击启动 start_for_win.bat
服务端实现
view : tp5\application\index\view\index.html
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>workerman-chat PHP聊天室 Websocket(HTLM5/Flash)+PHP多进程socket实时推送技术</title>
<script type="text/javascript">
//WebSocket = null;
</script>
<link href="__PUBLIC__/chat/css/bootstrap.min.css" rel="stylesheet">
<link href="__PUBLIC__/chat/css/style.css" rel="stylesheet">
<!-- Include these three JS files: -->
<script type="text/javascript" src="__PUBLIC__/chat/js/swfobject.js"></script>
<script type="text/javascript" src="__PUBLIC__/chat/js/web_socket.js"></script>
<script type="text/javascript" src="__PUBLIC__/chat/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 = "__PUBLIC__/chat/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'] : ?>"}';
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(/\n/g,'\\n').replace(/\r/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'])> ? intval($_GET['room_id']):; ?>)<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> </div>
</div>
</div>
<script type="text/javascript">var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F7b1919221e89d2aa5711e4deb935debd' type='text/javascript'%3E%3C/script%3E"));</script>
</body>
</html>
运行结果截图
linux版安装
a) 安装thinkphp5:
composer create-project topthink/think tp5 --prefer-dist
b) 进入tp5的目录,安装linux版本的workerman:
composer require topthink/think-worker
c) 安装linux版本的gateway:
composer require workerman/gateway-worker-for-win
关键部分,服务端实现
控制器 app\index\controller\Gate
<?php
/**
* linux workerman例子测试
* 需要在Linux系统控制台进行启动,启动文件位于根目录的start.php文件中
* Windows无法进行同时启动多个协议
* 由于PHP-CLI在windows系统无法实现多进程以及守护进程,所以windows版本Workerman建议仅作开发调试使用。
*/
namespace app\index\controller;
use Workerman\Worker;
use GatewayWorker\Gateway;
use GatewayWorker\Register;
use GatewayWorker\BusinessWorker;
class Gate
{
/**
* 构造函数
* @access public
*/
public function __construct(){ //初始化各个GatewayWorker
//初始化register register 服务必须是text协议
$register = new Register('text://0.0.0.0:1236'); //初始化 bussinessWorker 进程
$worker = new BusinessWorker();
// worker名称
$worker->name = 'ChatBusinessWorker';
// bussinessWorker进程数量
$worker->count = ;
// 服务注册地址
$worker->registerAddress = '127.0.0.1:1236';
//设置处理业务的类,此处制定Events的命名空间
$worker->eventHandler = 'app\index\controller\Events';
// 初始化 gateway 进程
$gateway = new Gateway("websocket://0.0.0.0:7272");
// 设置名称,方便status时查看
$gateway->name = 'ChatGateway';
$gateway->count = ;
// 分布式部署时请设置成内网ip(非127.0.0.1)
$gateway->lanIp = '127.0.0.1';
// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000
// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口
$gateway->startPort = ;
// 心跳间隔
$gateway->pingInterval = ;
// 心跳数据
$gateway->pingData = '{"type":"ping"}';
// 服务注册地址
$gateway->registerAddress = '127.0.0.1:1236'; //运行所有Worker;
Worker::runAll();
}
}
入口文件
文件: start.php
<?php
/**
* workerman + GatewayWorker
* 此文件只能在Linux运行
* run with command
* php start.php start
*/
ini_set('display_errors', 'on');
if(strpos(strtolower(PHP_OS), 'win') === )
{
exit("start.php not support windows.\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('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','chat/Gate');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';
启动程序
php start.php start
客户端跟Windows一样就可以了
workerman官网:http://www.workerman.net/
workerman文档:http://doc3.workerman.net/
GatewayWorker文档:http://doc3.workerman.net/
thinkphp5+GatewayWorker+Workerman的更多相关文章
- thinkPHP5扩展workerman
-安装workerman 首先通过 composer 安装 composer require topthink/think-worker -vvv 如果报错: Installation failed, ...
- thinkphp5使用workerman定时器定时爬取某站点新闻资讯等内容
1.首先通过 composer 安装workerman,在thinkphp5完全开发手册的扩展->coposer包->workerman有详细说明: #在项目根目录执行以下指令compos ...
- thinkphp5使用workerman的定时器定时任务在某一个时间执行
1.首先通过 composer 安装workerman,在thinkphp5完全开发手册的扩展->coposer包->workerman有详细说明: #在项目根目录执行以下指令compos ...
- Gateway-Worker启动失败或者启动无法正常使用的几种方法
Workerman是一款开源高性能异步PHP socket即时通讯框架.支持高并发,超高稳定性,被广泛的用于手机app.移动通讯,微信小程序,手游服务端.网络游戏.PHP聊天室.硬件通讯.智能家居.车 ...
- workerman程序调试
现象1 启动后报错类似如下: php start.php start PHP Warning: stream_socket_server(): unable to connect to tcp://x ...
- thinkphp5保存远程图片到本地
代码 protected function saveImg($imgUrl){ $ext=strrchr($imgUrl,'.'); if(!in_array($ext,['.jpg','.png', ...
- Swoole和Workerman到底选谁?
Swoole:面向生产环境的 PHP 异步网络通信引擎 使 PHP 开发人员可以编写高性能的异步并发 TCP.UDP.Unix Socket.HTTP,WebSocket 服务.Swoole 可以广泛 ...
- TP5整合 WorkerMan 以及 GatewayWorker
TP5整合GatewayWorker Windows版安装 a)使用composer create-project topthink/think testTG,来安装thinkphp5. b)进入t ...
- 02 workerman之GatewayWorker简单的demo 实现两端发送消息
前端代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <t ...
随机推荐
- 用户态和内核态&操作系统
用户态和内核态 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序. 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被 ...
- Docker搭建 oracle
1-1.docker run -d -p 11521:1521 --name sf2_oracle11g 镜像ID # -p:端口映射,此处映射主机端口 1-2.查看启动 docker logs - ...
- vue中:key 和react 中key={} 的作用,以及ref的特性?
vue中:key 和react 中key={} 为了给 vue 或者react 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性 一句话概括就是 ...
- SDL系列之 - 用画直线的方法来画正弦曲线
线段长度无限短后就成为点,所以,现在让我们用画直线的方法来画正弦曲线吧 #include <SDL.h> #include <stdlib.h> #include <st ...
- pytest-mark跳过
import pytestimport sysenvironment='android' @pytest.mark.skipif(environment=="android",re ...
- log4cplus TimeBasedRollingFileAppender
参考自:http://blog.csdn.net/u010607621/article/details/54944696 对于TimeBasedRollingFileAppender 这个日志appe ...
- Java 二叉树遍历相关操作
BST二叉搜索树节点定义: /** * BST树的节点类型 * @param <T> */ class BSTNode<T extends Comparable<T>&g ...
- grep命令 一 文本搜索工具
使用正则表达式搜索文本,并把匹配的行打印出来.使用权限是所有用户. 基本使用 grep [option] pattern filename: pattern如果是表达式或者超过两个单词的, 需要用引号 ...
- (转)C++实现RTMP协议发送H.264编码及AAC编码的音视频,摄像头直播
转:http://www.cnblogs.com/haibindev/archive/2011/12/29/2305712.html C++实现RTMP协议发送H.264编码及AAC编码的音视频 RT ...
- hdu4352-XHXJ's LIS状压DP+数位DP
(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦 题意:传送门 原题目描述在最下面. 在区间内把整数看成一个阿拉伯数字的集合,此集合中最长严格上升子序列的长度为k的个数. 思路: ...