幻之纪元·网游先锋

TwoThink配置GatewayWorker

安装twothink进入网站项目路径,使用git克隆

git clone “https://gitee.com/ypwl/TwoThink.git”克隆完毕会在项目目录生成TwoThink文件夹,如果你想把twothink放在项目根目录,只需要进入TwoThink将其中文件移动出来即可。

配置apache设置网站指向twothink的public文件夹,这里就不多说了。

TwoThink开发使用可以参考TwoThink的文档

浏览器打开http://你的域名/install.php即可进行安装,安装过程不再详细描述类似OneThink;

安装GatewayWorker使用composer方式安装GatewayWorker,由于twoThink基于thinkphp5.0,think-worker只能安装1.0版本的,

进图twothink项目目录,安装workerman

composer require topthink/think-worker:1.*安装gateway

composer require workerman/gateway-worker-for-win新建或编辑build.php

return [

// 生成应用公共文件

'__file__' => ['common.php', 'config.php', 'database.php'],

// 定义demo模块的自动生成 (按照实际定义的文件名生成)

'chat' => [

'__file__' => ['common.php'],

'__dir__' => ['behavior', 'controller', 'model', 'view'],

'controller' => ['Index', 'Events', 'Gate'],

'model' => ['User', 'UserType'],

'view' => ['index/index'],

],

// 其他更多的模块定义

];拷贝至application目录下运行

php think build显示Successed即为创建成功。

进入application/chat/controller目录,编辑Events控制器

namespace app\chat\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'=>"对你说: ".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'] = "你对".htmlspecialchars($message_data['to_client_name'])."说: ".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));

}

}

}编辑Gate控制器

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 = 'ChatBusinessWorker';

// bussinessWorker进程数量

$worker->count = 4;

// 服务注册地址

$worker->registerAddress = '127.0.0.1:1236';

//设置处理业务的类,此处制定Events的命名空间

$worker->eventHandler = 'app\chat\controller\Events';

// 初始化 gateway 进程

$gateway = new Gateway("websocket://0.0.0.0:9501");

// 设置名称,方便status时查看

$gateway->name = 'ChatGateway';

$gateway->count = 4;

// 分布式部署时请设置成内网ip(非127.0.0.1)

$gateway->lanIp = '127.0.0.1';

// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000

// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口

$gateway->startPort = 2300;

// 心跳间隔

$gateway->pingInterval = 10;

// 心跳数据

$gateway->pingData = '{"type":"ping"}';

// 服务注册地址

$gateway->registerAddress = '127.0.0.1:1236';

//运行所有Worker;

Worker::runAll();

}

}编辑入口文件 start.php,将入口文件放在public文件夹内

/**

* 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';进入public目录使用以下命令启动

php start.php start

此处参考http://www.thinkphp.cn/code/3937.html

Apache2反向代理wsApache反向代理之后可以隐藏端口号直接ws://域名需要安装proxy和proxy_wstunnel模块输入命令

sudo a2enmod proxy

sudo a2enmod proxy_wstunne然后重启apache服务

service apache2 restart编辑配置

我的apache是使用apt直接安装的,其配置文件在/etc/apache2/sites-enabled路径下,

输入nano 000-default.conf进行编辑,找到下方位置,做出如下修改。

ServerName 你的域名

DocumentRoot 项目路径/twothink/public

ServerAdmin admin@admin.com

ErrorLog ${APACHE_LOG_DIR}/error.log

CustomLog ${APACHE_LOG_DIR}/access.log combined

ProxyPass / ws://localhost:9501/

ProxyPassReverse / ws://localhost:9501/

注意:该修改一旦生效则对应域名只能以ws协议请求,不能再使用http协议。Crtl+O进行保存,crtl+x退出编辑器,重启服务器即可参考文章https://blog.csdn.net/sunhuwh/article/details/48215831

配置wss这里以服务器开启ssl为前提。

配置ws+ssl需要ssl证书,可以自己生成也可以购买证书,将证书文件放在/etc/apache2/certs/路径下(根据个人喜好,只要配置的时候路径正确即可)

配置/etc/apache2/sites-enabled/default-ssl.conf

在对应域名配置中进行如下设置:

# Proxy Config

SSLProxyEngine on

ProxyRequests Off

ProxyPass /wss ws://127.0.0.1:9501

ProxyPassReverse /wss ws://127.0.0.1:9501

# 添加 SSL 协议支持协议,去掉不安全的协议

SSLProtocol all -SSLv2 -SSLv3

# 修改加密套件如下

SSLCipherSuite HIGH:!RC4:!MD5:!aNULL:!eNULL:!NULL:!DH:!EDH:!EXP:+MEDIUM

SSLHonorCipherOrder on

# 证书公钥配置

SSLCertificateFile 你的证书路径/your.pem

# 证书私钥配置

SSLCertificateKeyFile 你的证书路径/your.key

# 证书链配置,

SSLCertificateChainFile 你的证书路径/chain.pem保存重启apache

在chrome中进行测试

// 证书是会检查域名的,请使用域名连接

ws = new WebSocket("wss://域名");

ws.onopen = function() {

alert("连接成功");

ws.send('tom');

alert("给服务端发送一个字符串:tom");

};

ws.onmessage = function(e) {

alert("收到服务端的消息:" + e.data);

};