公司需要实现一个功能:pc端点击按钮,调用接口,并且将pc端请求的数据发送给app端,使app获取数据后执行后续功能。
环境:宝塔环境+tp5.0+php7.1
一开始用了Workerman搞了2天后发现开发难度较大,而且不稳定。后来发现了GatewayWorker,使用简单,内置很多类可以直接调用,非常友好。GatewayWorker是基于Workerman实现的一个更完善的专门用于实现TCP长连接的项目框架。
一、宝塔禁用函数
禁用函数putenv、pcntl_signal_dispatch、pcntl_wai、pcntl_signal、pcntl_alarm、pcntl_fork。
二、tp5安装GatewayWorker,进入根目录(不是public目录)
1.安装GatewayWorker
composer require workerman/gateway-worker: "3.0.22"
2.安装workman
composer require topthink/think-worker: "1.0.0"
3.安装gatewayclient
composer require workerman/gatewayclient: "^3.1"
三、在项目根目录新建 start.php
ThinkPHP 5.1 使用该文件
<?php
/**
* workerman + GatewayWorker
* 此文件只能在Linux运行
* run with command
* php start.php start
*/
// [ 应用入口文件 ]
namespace think;
ini_set('display_errors', 'on');
if(strpos(strtolower(PHP_OS), 'win') === 0)
{
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('GLOBAL_START', 1);
// 加载基础文件
require __DIR__ . '/thinkphp/base.php';
// 执行应用并响应
Container::get('app')->bind('chat/Gate')->run()->send();
ThinkPHP 5.0 使用该文件
<?php
/**
* workerman + GatewayWorker
* 此文件只能在Linux运行
* run with command
* php start.php start
*/
ini_set('display_errors', 'on');
if(strpos(strtolower(PHP_OS), 'win') === 0)
{
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';
四、创建控制器 chat/Gate
<?php
/**
* 需要在Linux系统控制台进行启动,启动文件位于根目录的start.php文件中
* Windows无法进行同时启动多个协议
* 由于PHP-CLI在windows系统无法实现多进程以及守护进程,所以windows版本Workerman建议仅作开发调试使用。
*/
namespace app\chat\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 = 'ChatBusinessWorker22';
// bussinessWorker进程数量
$worker->count = 4;
// 服务注册地址
$worker->registerAddress = '127.0.0.1:1236';
//设置处理业务的类,此处制定Events的命名空间
$worker->eventHandler = 'app\chat\controller\Events';
$gateway = new Gateway("websocket://0.0.0.0:9502");
$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();
}
}
五、创建控制器 chat/Events
<?php
namespace app\chat\controller;
use think\Controller;
use think\Db;
use think\Session;
/**
* 用于检测业务代码死循环或者长时间阻塞等问题
* 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
* 然后观察一段时间workerman.log看是否有process_timeout异常
*/
//declare(ticks=1);
/**
* 聊天主逻辑
* 主要是处理 onMessage onClose
*/
use \GatewayWorker\Lib\Gateway;
class Events
// class Events extends Controller
{
/**
* 当客户端的连接上发生错误时触发
* @param $connection
* @param $code
* @param $msg
*/
public function onError($connection, $code, $msg)
{
echo "error $code $msg\n";
}
public static function onWebSocketConnect($client_id, $data)
{
if (isset($data['get']['user_id']) && $data['get']['user_id']){
$user_id = $data['get']['user_id'];
Gateway::bindUid($client_id, $user_id);
Gateway::sendToClient($client_id, '你连接了我 ID:'.$user_id);
}
}
/**
* 当客户端连接时触发
* 如果业务不需此回调可以删除onConnect
*
* @param int $client_id 连接id
*/
public static function onConnect($client_id)
{
// 向所有人发送
//Gateway::sendToAll("$client_id login\r\n");
}
public static function onWorkerStart($businessWorker)
{
}
/**
* 有消息时
* @param int $client_id
* @param mixed $message
*/
public static function onMessage($client_id, $message)
{
}
/**
* 当客户端断开连接时
* @param integer $client_id 客户端id
*/
public static function onClose($client_id)
{
}
}
六、pc端调用接口
<?php
namespace app\index\controller;
use \GatewayWorker\Lib\Gateway;
class Index
{
public function index()
{
try {
$user_id = input('post.user_id');
$mobile = input('post.mobile');
$data = array('user_id' => $user_id, 'mobile' => $mobile);
// 检查用户是否连接
if(Gateway::isUidOnline($user_id)){
Gateway::sendToUid($user_id, json_encode($data));
}else{
return resultArray(['error' => '连接失败']);
}
return resultArray(['data' => '连接成功']);
} catch (\Exception $e) {
return resultArray(['error' => '连接失败']);
}
}
}
七、启动websocket程序
1、防火墙打开9502、1236端口,执行下面命令
//运行 测试运行
php start.php start
//linux运行 长链接
php start.php start -d
//停止
php start.php stop
//检测端口是否以被占用
netstat -an | grep 80
//关闭某个进程
sudo kill -9 进程ID
如果修改文件后一定要先停止在运行一下文件,否则不生效
我这里默认php版本是7.3,所以启动时需要指定php版本
查看宝塔默认php版本。网站-》高级设置-》PHP命令版本
指定版本执行命令
#进入项目根目录,不是public目录
cd /www/wwwroot/crm1002`
#指定php版本开起
/www/server/php/71/bin/php start.php start -d`
八、宝塔里配置wss。 使用wss访问,能够更加稳定
必须先配置ssl
1、配置文件添加内容:
代码如下:
map $http_upgrade $connection_upgrade {
default upgrade; '' close;
}
# 这里的backend可以改成自己需要的名称,我自己用的是wss。
upstream wss {
# 这里的localhost是域名,也可以是外网ip,9502是我ws开启的端口。
server localhost:9502;
}
2.设置伪静态
代码如下:
location /wss {
proxy_pass http://127.0.0.1:9502;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
rewrite /wss/(.*) /$1 break;
proxy_redirect off;
}
九、使用apipost或者postman模拟请求
访问路由为
wss://域名/wss?user_id=126
参考文档:
宝塔Nginx配置wss:https://www.kancloud.cn/bishengzhu/twword/2056626
ThinkPHP 5 gatewayworker文档:https://www.kancloud.cn/phpcp/thinkphp5/2287102
gateway-worker使用文档:https://www.workerman.net/doc/gateway-worker/bind-uid.html