為什麼要寫這篇文章?
我學習Workman好幾次了,每次都失敗(沒做成想要的功能,原諒我比較笨)。但是這次也花了好幾個小時,把之前沒做成的功能實現了。其實就是兩個簡單的功能:一對一傳送訊息,廣播訊息(群聊)。這個功能用swoole早都實現了,也是由於之前一直想用 think-worker 的原因,想想還是得自己琢磨才行,人家做好的框架或許是個閹割版。
別問我為什麼不用swoole,因為 workman 可以在Windows中執行。
(1)首先,得簡單說說 thinkphp+workerman 的安裝。
安裝 thinkphp5.1
composer create-project topthink/think=5.1.x-dev tp5andworkman
安裝 think-worker
composer require topthink/think-worker=2.0.*
直接安裝 workman
composer require workerman/workerman
(2)我們先看 think-worker 的程式碼
-
config/worker_server.php
-
先來個伺服器廣播訊息的示例,每10秒鐘定時廣播一條訊息
'onWorkerStart' => function ($worker) {
\Workerman\Lib\Timer::add(10, function()use($worker){
// 遍歷當前程式所有的客戶端連線,傳送自定義訊息
foreach($worker->connections as $connection){
$send['name'] = '系統資訊';
$send['content'] = '這是一個定時任務資訊';
$send['time'] = time();
$connection->send(json_encode($send));
}
});
}
但是在 onMessage 時,我們獲取不到 $worker 物件,所以無法廣播訊息。
'onMessage' => function ($connection, $data) {
$origin = json_decode($data,true);
$send['name'] = '廣播資料';
$send['content'] = $origin['content'];
$message = json_encode($send);
foreach($worker->connections as $connection)
{
$connection->send($message);
}
}
嘗試了各種方法,貌似都不行
'onMessage' => function ($connection, $data)use($worker) {
// 這樣是獲取不到 $worker 物件的
// ...省略程式碼
}
所以只能拋棄 thinkphp 給我們封裝的 think-worker 框架,得自己寫,(或者修改框架內部程式碼)
修改框架內部的程式碼:/vendor/topthink/think-worker/src/command/Server.php
,主要是把 onMessage 方法自己加進去
use() 就是把外部變數傳遞到函式內部使用,或者使用global $worker
$worker = new Worker($socket, $context);
$worker->onMessage = function ($connection, $data)use($worker) {
$origin = json_decode($data,true);
$send['name'] = '廣播資料';
$send['content'] = $origin['content'];
$send['uid'] = $connection->uid;
$message = json_encode($send);
foreach($worker->connections as $connection)
{
$connection->send($message);
}
};
這樣,我們就能夠獲取到 $worker 物件了
$worker->onMessage = function ($connection, $data)use($worker) { ... }
(3)$connection 繫結 uid
其實你早都已經看出,$worker->connections 獲取到的是當前所有使用者的連線,connections 即為其中一個連結。
記錄websocket連線時間:
$worker->onConnect = function ($connection) {
$connection->login_time = time();
};
獲取websocket連線時間:
$worker->onMessage = function ($connection, $data)use($worker) {
$login_time = $connection->login_time;
};
由此可以看出,我們可以把資料繫結到 $connection 連線的一個屬性,例如:
$connection->uid = $uid;
當JavaScript端在連線websocket伺服器成功後,即把自己的 uid 立馬傳送服務端繫結:
$worker->onMessage = function ($connection, $data)use($worker) {
$origin = json_decode($data,true);
if(array_key_exists('bind',$origin)){
$connection->uid = $origin['uid'];
}
};
(4)單播傳送訊息,即自定義傳送
$worker->onMessage = function ($connection, $data)use($worker) {
$origin = json_decode($data,true);
$sendTo = $origin['sendto']; // 需要傳送的對方的uid
$content = $origin['content']; // 需要傳送到對方的內容
foreach($worker->connections as $connection)
{
if( $connection->uid == $sendTo){
$connection->send($content);
}
}
};
到此,已經完成基於 workman 的自定義物件傳送訊息。
由於該php檔案存放於composer中,只需要把該檔案複製出來,放到application/command
,修改名稱空間,即可儲存到自己的專案中
(5)對比swoole
1、workman可以在windows系統中執行,swoole則不能。
2、workman:$worker->connections獲取所有連線,$connection->id獲取自己的連線id;swoole:$server->connections獲取所有連線,$connection->fd獲取自己的連線id。
3、workman啟動時執行 onWorkerStart 方法,可以把定時器寫入到裡面;swoole 使用 WorkerStart 啟動定時器。
僅僅於聊天室或者定時器而言,workman 還是比較方便的。