walkor大神,目前需求是這樣的:
有一群商家在后臺網(wǎng)頁處理批量導入產(chǎn)品 -》 服務器接受請求 -》 開始foreach一個一個處理導入請求;
我現(xiàn)在想每成功導入一個就推送到前臺顯示已經(jīng)導入成功,直到全部導入自動結束推送。
看了聊天室代碼,消息推送都是靠前端js+event.php,我想直接在php里面不需要onMessage觸發(fā).
我從下午看到現(xiàn)在文檔,也看了很多問答,依然非常糊涂,不奢望給整段代碼,但是希望walkor大神給點思路。
后端代碼
push.php
<?php
use Workerman\Worker;
require_once './Workerman/Autoloader.php';
// 初始化一個worker容器,監(jiān)聽1234端口
global $worker;
$worker = new Worker('websocket://0.0.0.0:1234');
// 這里進程數(shù)必須設置為1
$worker->count = 1;
// worker進程啟動后建立一個內(nèi)部通訊端口
$worker->onWorkerStart = function($worker)
{
// 開啟一個內(nèi)部端口,方便內(nèi)部系統(tǒng)推送數(shù)據(jù),Text協(xié)議格式 文本+換行符
$inner_text_worker = new Worker('Text://0.0.0.0:5678');
$inner_text_worker->onMessage = function($connection, $buffer)
{
global $worker;
// $data數(shù)組格式,里面有uid,表示向那個uid的頁面推送數(shù)據(jù)
$data = json_decode($buffer, true);
$uid = $data['uid'];
// 通過workerman,向uid的頁面推送數(shù)據(jù)
$ret = sendMessageByUid($uid, $data['percent']);
// 返回推送結果
$connection->send($ret ? 'ok' : "uid $uid not online");
};
$inner_text_worker->listen();
};
// 新增加一個屬性,用來保存uid到connection的映射
$worker->uidConnections = array();
// 當有客戶端發(fā)來消息時執(zhí)行的回調(diào)函數(shù)
$worker->onMessage = function($connection, $data)use($worker)
{
// 判斷當前客戶端是否已經(jīng)驗證,既是否設置了uid
if(!isset($connection->uid))
{
// 沒驗證的話把第一個包當做uid(這里為了方便演示,沒做真正的驗證)
$connection->uid = $data;
/* 保存uid到connection的映射,這樣可以方便的通過uid查找connection,
* 實現(xiàn)針對特定uid推送數(shù)據(jù)
*/
$worker->uidConnections[$connection->uid] = $connection;
return;
}
};
// 當有客戶端連接斷開時
$worker->onClose = function($connection)use($worker)
{
global $worker;
if(isset($connection->uid))
{
// 連接斷開時刪除映射
unset($worker->uidConnections[$connection->uid]);
}
};
// 向所有驗證的用戶推送數(shù)據(jù)
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}
// 針對uid推送數(shù)據(jù)
function sendMessageByUid($uid, $message)
{
global $worker;
if(isset($worker->uidConnections[$uid]))
{
$connection = $worker->uidConnections[$uid];
$connection->send($message);
return true;
}
return false;
}
// 運行所有的worker(其實當前只定義了一個)
Worker::runAll();
啟動后端服務
php push.php start -d
前端接收推送的js代碼
var ws = new WebSocket('ws://127.0.0.1:1234');
ws.onopen = function(){
var uid = 'uid1';
ws.send(uid);
};
ws.onmessage = function(e){
alert(e.data);
};
后端推送消息的代碼
// 建立socket連接到內(nèi)部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的數(shù)據(jù),包含uid字段,表示是給這個uid推送
$data = array('uid'=>'uid1', 'percent'=>'88%');
// 發(fā)送數(shù)據(jù),注意5678端口是Text協(xié)議的端口,Text協(xié)議需要在數(shù)據(jù)末尾加上換行符
fwrite($client, json_encode($data)."\n");
// 讀取推送結果
echo fread($client, 8192);
這里的uid不一定是用戶的id,也可以理解為任務id即 taskid
記得開放1234 5678 兩個端口的防火墻。如果是云服務器,還要開放這兩個端口的安全組。
以上代碼親測可以直接使用
@1 群主經(jīng)測試您給的后端代碼前端js,workerman推送代碼只對一個頁面有效,并不是對所有打開的頁面有效,我打算對所有頁面有效,我該怎么做呢?或者我如何設置成可以向所有打開頁面的uid推送。希望群主給點思路呀。
@1 群主push.php里面有broadcast方法向所有頁面推送,但是我前端js中怎樣把所有頁面uid傳同一個呢?如果所有頁面uid傳同一個的話那后端php代碼是不是不用向push.php發(fā)送指定的uid了呢?
@1 群主我補充一下我的應用場景呀 我是在不同瀏覽器,或者不同的電腦下相同或不同的瀏覽器打開同一個頁面(網(wǎng)址一樣)讓他們都能推送,我已經(jīng)設置了定時刷新。我希望每個用戶打開這個界面都能定時看到推送。
@6607:Linux不能用應該是防火墻沒設置這個端口導致的,在防火墻加這條規(guī)則,然后重啟防火墻就可以了:-A INPUT -p tcp -m tcp --dport 1234 -j ACCEPT,我剛好遇到這個問題,這樣解決的
執(zhí)行后端推送代碼時,出現(xiàn) unable to connect, connect refused. 請問這是什么原因造成的尼?我已經(jīng)更換了多個端口進行測試,依然是同樣的提示。還有什么測試方法和手段來找出原因嘛?
后端推送消息的代碼
// 建立socket連接到內(nèi)部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT);
// 推送的數(shù)據(jù),包含uid字段,表示是給這個uid推送
$data = array('uid'=>'uid1', 'percent'=>'88%');
// 發(fā)送數(shù)據(jù),注意5678端口是Text協(xié)議的端口,Text協(xié)議需要在數(shù)據(jù)末尾加上換行符
fwrite($client, json_encode($data)."\n");
// 讀取推送結果
echo fread($client, 8192);
首先,我先建一個 ump worker , 再在 work 進程啟動后建立一個內(nèi)部通訊端口。然后,我再寫 php socket 腳本來反問內(nèi)部通訊端口,提示 connect refused。防火墻是否阻擋,端口是否被占用,服務是否開啟,客戶端鏈接的 ip 都這幾個問題都確認沒有問題。
結果,卻是一直提示 connection refused.
use Workerman\Worker;
include './Workerman/Autoloader.php';
$worker = new Worker("udp://192.168.50.190:8800");
//var_dump($worker);
$worker->count = 1;
$worker->onWorkerStart = function ($worker)
{
// 用于 Laravel 與 Workerman 的內(nèi)部通訊
$inner_text_worker = new Worker ("text://192.168.50.190:5678");
// 接收到 Laravel 請求信息,就向設備發(fā)起 UDP 請求
$inner_text_worker->onMessage = function ($connection, $arr_data)
{
$connection->send('success udp');
};
};
// 接收 UDP 請求,如:心跳
$worker->onMessage = function ($connection, $data)
{
echo $data . "<br/>";
$client_ip = $connection->getRemoteIp();
$client_port = $connection->getRemotePort();
$connection->send(strrev($data));
};
}}
// 開啟內(nèi)部通訊端口打印輸出的對象:object(Workerman\Worker)#6 (24) {
=>
int(0)
=>
string(4) "none"
=>
int(1)
=>
string(0) ""
=>
string(0) ""
=>
bool(true)
=>
bool(false)
=>
NULL
=>
NULL
=>
object(Closure)#7 (1) {
=>
_RECURSION_
}
=>
NULL
=>
NULL
=>
NULL
=>
NULL
=>
NULL
=>
NULL
=>
string(3) "tcp"
=>
array(0) {
}
=>
string(0) ""
=>
string(37) "/Library/WebServer/Documents/camerawk"
=>
NULL
=>
string(26) "text://192.168.50.190:5678"
=>
resource(17) of type (stream-context)
=>
string(32) "000000000f4c6bca0000000043696ce7"
}
// php socket 腳本對內(nèi)部端口通訊發(fā)起請求,報錯:stream_socket_client(): unable to connect to tcp://192.168.50.190:5678 (Connection refused)
問題:如果我 new 的 worker 是監(jiān)聽是的 http 通訊 8080端口,當有接收到信息時,那么 workerman 的onMessage 方法執(zhí)行 $connection->send("message") 。
我的疑問時 $connection->send("message") 發(fā)送的信息,會以什么端口,從服務器發(fā)出去呢?
$worker->onMessage = function($connection, $data) use ($worker)
{
$connection->send("返回信息");
};
上面的沒看得太明白,push.php是運行在workman里面的 后端推送消息代碼是運行在web后臺的對嗎,通過后端推送消息代碼調(diào)用push.php.是這個思路嗎
要實現(xiàn)我這種模式 問號部分該用什么方法實現(xiàn)呢
// 建立socket連接到內(nèi)部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的數(shù)據(jù),包含uid字段,表示是給這個uid推送
$data = array('uid'=>'uid1', 'percent'=>'88%');
// 發(fā)送數(shù)據(jù),注意5678端口是Text協(xié)議的端口,Text協(xié)議需要在數(shù)據(jù)末尾加上換行符
fwrite($client, json_encode($data)."\n");
// 讀取推送結果
echo fread($client, 8192);
這里不是寫得很清楚嘛?
workerman 的文檔里面也有
http://doc3.workerman.net/315240
推薦使用 GatewayWorker 的方式
本質(zhì)就是進程間通訊而已
$inner_text_worker = new Worker('Text://0.0.0.0:5678');
第一次運行沒有問題,但第二次運行
fwrite($client, json_encode($data)."\n");
這里會報錯,換一個端口運行,又可以了,這是為什么呢?
use Yii;
use yii\console\Controller;
use \Workerman\Worker;
require_once 'vendor/autoload.php';
class WorkerController extends Controller {
public function actionPush(){
// 初始化一個worker容器,監(jiān)聽1234端口
require_once dirname(Yii::$app->basePath).'/vendor/workerman/workerman/Autoloader.php';
$worker = new Worker('websocket://127.0.0.1:1234');
// 這里進程數(shù)必須設置為1
$worker->count = 1;
// worker進程啟動后建立一個內(nèi)部通訊端口
$worker->onWorkerStart = function($worker)
{
// 開啟一個內(nèi)部端口,方便內(nèi)部系統(tǒng)推送數(shù)據(jù),Text協(xié)議格式 文本+換行符
$inner_text_worker = new Worker('http://127.0.0.1:5678');
$inner_text_worker->onMessage = function($connection, $buffer)
{
global $worker;
// $data數(shù)組格式,里面有uid,表示向那個uid的頁面推送數(shù)據(jù)
$data = json_decode($buffer, true);
$uid = $data;
// 通過workerman,向uid的頁面推送數(shù)據(jù)
$ret = $this->sendMessageByUid($uid, $buffer);
// 返回推送結果
$connection->send($ret ? 'ok' : 'fail');
};
$inner_text_worker->listen();
};
// 新增加一個屬性,用來保存uid到connection的映射
$worker->uidConnections = array();
// 當有客戶端發(fā)來消息時執(zhí)行的回調(diào)函數(shù)
$worker->onMessage = function($connection, $data)use($worker)
{
// 判斷當前客戶端是否已經(jīng)驗證,既是否設置了uid
if(!isset($connection->uid))
{
// 沒驗證的話把第一個包當做uid(這里為了方便演示,沒做真正的驗證)
$connection->uid = $data;
/* 保存uid到connection的映射,這樣可以方便的通過uid查找connection,
* 實現(xiàn)針對特定uid推送數(shù)據(jù)
*/
$worker->uidConnections = $connection;
return;
}
};
// 當有客戶端連接斷開時
$worker->onClose = function($connection)use($worker)
{
global $worker;
if(isset($connection->uid))
{
// 連接斷開時刪除映射
unset($worker->uidConnections);
}
};
// 運行所有的worker(其實當前只定義了一個)
Worker::runAll();
}
// 向所有驗證的用戶推送數(shù)據(jù)
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}
// 針對uid推送數(shù)據(jù)
function sendMessageByUid($uid, $message)
{
global $worker;
if(isset($worker->uidConnections))
{
$connection = $worker->uidConnections;
$connection->send($message);
return true;
}
return false;
}
}
stream_socket_client(): unable to connect to tcp://127.0.0.1:5678 (???????????????????????
public function pushma($msg){
header("Content-Type: text/html; charset=UTF-8");
// 建立socket連接到內(nèi)部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的數(shù)據(jù),包含uid字段,表示是給這個uid推送
$data = array('uid'=>'uid1', 'percent'=>$msg);
// 發(fā)送數(shù)據(jù),注意5678端口是Text協(xié)議的端口,Text協(xié)議需要在數(shù)據(jù)末尾加上換行符
fwrite($client, json_encode($data)."\n");
// 讀取推送結果
echo fread($client, 8192);
}
這是調(diào)用代碼
[attach]574[/attach]
這個斷開,是否還要用心跳來判斷,還是內(nèi)部自動會判斷
如果沒發(fā)消息過來了,會不會就自己會斷開
這個demo心跳作用是用來防止鏈接由于長時間不活躍被路由節(jié)點防火墻關閉。如果你預計這個鏈接要維持很長時間(超過一分鐘),需要客戶端定時發(fā)一點數(shù)據(jù)給服務端,用來保持鏈接活躍。服務端onMessage里判斷下如果是心跳消息忽略即可。
如果鏈接預計低于一分鐘,可以不用發(fā)心跳。
@1:walkor大神,正如我昨天提的那個問題,如果后端消息不停推送,在onMessage里面是沒法收到消息的,只能等后端消息推送完畢后,才能接受到,可能這時候已經(jīng)超過了后端設置的心跳時間(比如1分鐘),就會在onclose主動關閉客戶端連接,然后客戶端重新連接,這中間的數(shù)據(jù)就會丟失,這種情況怎么解決呢?
感覺很不穩(wěn)定,不知應該怎么來調(diào)試
客戶端寫了心跳,比之前的沒心跳的,連接情況更差
消息返回成功的很少,而且即使返回成功,但客戶端,還是沒有接收到數(shù)據(jù)
以前一兩小時,發(fā)生三四次失敗,現(xiàn)在是經(jīng)常失敗
寫的比較好,測試沒問題,很好解決了PHP客戶端與web socket端之間的數(shù)據(jù)監(jiān)聽通訊
我測試的時候也發(fā)現(xiàn)這個情況,但原因是因為時間長了和服務器斷開連接了,這樣父進程weibsocket里原來的uid1已經(jīng)刪除了,這個時候php客戶端發(fā)送數(shù)據(jù),數(shù)據(jù)其實服務端的子進程接收到了,但由于沒有找到uid,所以php客戶端沒有收到回復。加上心跳,代碼邏輯上再優(yōu)化些應該可以解決。
看看這個workerman實戰(zhàn)視頻,保準都會了
http://study.163.com/course/introduction/1005015012.htm?share=2&shareId=400000000388007
總體上該方案非常優(yōu)秀,框架大概二個部分,第一個是websocket作為服務端接收和發(fā)送消息的父進程,不妨稱之為fatherWorker,比較巧妙的是在該對象里嵌套了一個TEXT協(xié)議的worker,不妨叫他childWorker,這個功能用一個PHP頁面就能解決。第二個是客戶端,該客戶端可以用本地建立一個簡單網(wǎng)站實現(xiàn),用來登錄界面操作,里面兩個頁面,第一個頁面是用來和服務端的父進程websocket建立連接,并發(fā)送UID的,這個頁面可以是普通的html頁面。第二個頁面是用來和服務端的子進程TEXT協(xié)議建立連接的,其作用是發(fā)送UID編號和數(shù)據(jù),這里要注意的是兩個頁面的UID號必須一致,否則發(fā)送失敗。
這樣,三個頁面,實現(xiàn)了WEB服務、SOCKET服務和TEXT協(xié)議服務三者聯(lián)動的效果,構思巧妙,非常優(yōu)秀!
workerman新手,根據(jù)自己入門經(jīng)歷的疑惑,講一下表面的小白使用問題,也是新手小白容易產(chǎn)生的疑惑:
可以確定,下載workerman最新版,不修改示例代碼,示例代碼是百分百可以在linux和windows都可以正常運行的。
windows中出現(xiàn)的問題:
(1)、執(zhí)行順序!一定不能錯:第一步,php push.php,第二步,瀏覽器的頁面上運行執(zhí)行js ,第三步,執(zhí)行stream_socket_client的5678端口的那段代碼
(2)、windows本機測試中,第二步的js,可以在任何一個瀏覽器的console中執(zhí)行,但是要注意,如果是打算把js放進自己寫的html頁面,是無法用本地windows的apache服務訪問頁面的,原因應該就是windows下php只能用一個進程,而php進程已經(jīng)被第一步占用。同樣,第三步,也不能用windows本機的apache請求頁面或者php命令行執(zhí)行php,可以通過telnet 127.0.0.1:5678的方式,輸入:{"uid":"uid1","percent":"2%"},瀏覽器js就可以收到了
(3)、這個代碼是可以調(diào)試的(好像是廢話)。可能對于小白而言,因為對workerman陌生一時忘記了怎么調(diào)試。其實和普通的php開發(fā)一樣,可以直接echo/var_dump打印輸出,也可以記錄到文件里。
telnet 127.0.0.1 5678 的命令下,也可以調(diào)試5678接收第三步消息的信息:push.php代碼里,在$connection->send($ret ? 'ok' : 'fail');前,加上自己要調(diào)試的內(nèi)容send即可,如:$connection->send($buffer);
? ?!! push.php調(diào)試代碼修改后,一定要重新啟動第一步(linux下restart/reload;windows下退出進程,重新執(zhí)行),才能生效
? ? linux下執(zhí)行php push.php start 后面不加-d,不讓后臺運行,可以查看打印出的調(diào)試輸出
(4)、一旦出現(xiàn)問題,檢查步驟:
? ? ? 1、telnet 127.0.0.1 1234
? ? ? ? ? ?telnet 127.0.0.1 5678
? ? ? ? ?如果不通,端口是否開啟,linux下檢查iptables
? ? ? 2、如果是第三步返回fail,一般是uid用戶連接斷開了,只需要瀏覽器重新執(zhí)行一下第二步js,在重新執(zhí)行第三步
向端口推送數(shù)據(jù)的時候,返回的一致是false;
$client = stream_socket_client('tcp://127.0.0.1:2345',$error,$errmsg,1);
var_dump($client);
//推送的數(shù)據(jù),包含的uid字段,表示給這個用戶uid推送
$data = array('uid'=>'uid1', 'percent'=>'88%');
fwrite($client, json_encode($data)."\n");
echo fread($client, 8192);
大佬呀,為啥向端口推送數(shù)據(jù)的時候,返回的一致是false呀
$client = stream_socket_client('tcp://127.0.0.1:2345',$error,$errmsg,1);
var_dump($client);
大家看看我寫的對著沒 通過http給客戶端推送tcp消息
https://github.com/phpyii/workerman-test/tree/master/test/tcp