我的博客文章網(wǎng)址:
http://www.jloongking.cn/tp50/public/blog/index/blog?blogid=93
由于最近做一個物聯(lián)網(wǎng)項目据过,該項目需要遠(yuǎn)程將溫度推送到服務(wù)器孝赫,并由服務(wù)器推送到web前臺窍仰,硬件可以利用tcp協(xié)議將數(shù)據(jù)上傳到到服務(wù)器武翎,但是由于不固定ip的原因块请,服務(wù)器是找不到web前端的亡呵,而在這個時候我們就需要利用到長連接workerman抬探,在之前我曾經(jīng)利用workerman進(jìn)行聊天軟件的編寫絮供,實現(xiàn)多人在線聊天功能衣吠,而在這之前我沒有將這個博客編寫成功,本次趁著這個機(jī)會杯缺,我將長連接的知識進(jìn)行了復(fù)習(xí)蒸播,正好博客完成時間沒有多久,于是將這次經(jīng)歷記錄在案萍肆,以備以后自己查看學(xué)習(xí)袍榆,也方便了看到這個博文并且可能需要用到該框架的同學(xué)們。最后塘揣,我需要感謝一位同學(xué)包雀,他在這個項目中幫助了我很多,亲铡。
在進(jìn)行該教程之前才写,我們需要了解一下workerman這個框架和thinkphp這個框架,thinkphp框架是PHP幾大框架之一奖蔓,想要了解的同學(xué)們可以到ThinkPHP5.0完全開發(fā)手冊上進(jìn)行文檔的閱讀赞草,以下簡稱tp5,tp5框架是國產(chǎn)為數(shù)不多的優(yōu)秀框架之一吆鹤,國產(chǎn)的哦主届!
而workerman則是在workerman官網(wǎng)上有詳細(xì)的介紹梆造,同學(xué)們可以到該網(wǎng)站上進(jìn)行手冊的查看魄缚。
在ThinkPHP5.0完全開發(fā)手冊里面有一篇介紹他們兩個融合的文章composer包workerman在這篇文章里介紹了tp5融合workerman的教程框往,但是介紹的過于簡潔,我試驗了兩次并沒有走通知允,而在這次的記錄中我將我的融合過程和這個方法進(jìn)行對比撒蟀,分析以前沒有走通的原因。
不論在哪個方法中温鸽,我們都需要將wokerman的包引入保屯,我們需要用到composer,沒有安裝composer的同學(xué)需要自行安裝。
第一步
導(dǎo)入workerman包:
composer?require?topthink/think-worker
如果windows服務(wù)器還要利用以下命令:
composer?require?workerman/workerman-for-win
如運行出現(xiàn)錯誤PHP Fatal error: Call to undefined function Workerman\Lib\pcntl_signal()配椭,需要刪除vendor\workerman\workerman虫溜,防止命名覆蓋。
當(dāng)包引入完成后股缸,我們會在項目根目錄下的vendor文件夾下看到workerman文件夾衡楞,這樣框架就引入了該項目,我們下一步需要配置服務(wù)的啟動和引用敦姻。
第二步
在這一步中我們需要將啟動服務(wù)文件放入到項目根目錄中瘾境,在根目錄中我們新建啟動服務(wù)文件server.php
代碼如下:
#!/usr/bin/env?php
<?php
define('APP_PATH',?__DIR__?.?'/application/');
//這是原來的代碼
//define('BIND_MODULE','push/Worker');
//這是我修改后的代碼
define('BIND_MODULE','push/Workertest/index');
//?加載框架引導(dǎo)文件
require?__DIR__?.?'/thinkphp/start.php';
這個代碼的意思是綁定workerman的模塊是/application/push/Worker這個控制器,但是由于我們沒有用tp5這個框架的引用方式這里我們將代碼改為如上所示镰惦,直接進(jìn)入到控制器的index方法迷守,在下一步我們會介紹為什么用這種方法。我們在下一步中就會定義模塊旺入,也就是實現(xiàn)功能的地方:/application/push/workertest.php
第三步
在這一步開始前我們來看一下tp5開發(fā)手冊中的worker.php兑凿,需要聲明的是我并沒用利用該方法。
????一共有一個變量和5個方法茵瘾,變量是定義端口和域名的礼华,方法是分別為,連接上時拗秘,服務(wù)開始時圣絮,接到信息時,錯誤時雕旨,斷開時的相應(yīng)處理方法扮匠。
代碼如下
<?php
namespace?app\push\controller;
use?think\worker\Server;
class?Worker?extends?Server{????
????protected?$socket?=?'websocket://push.app:2346';??
?????/**
?????*?收到信息
?????*?@param?$connection
?????*?@param?$data
?????*/
????public?function?onMessage($connection,?$data)
????{
????????$connection->send('我收到你的信息了');
????}????
????/**
?????*?當(dāng)連接建立時觸發(fā)的回調(diào)函數(shù)
?????*?@param?$connection
?????*/
????public?function?onConnect($connection)
????{
????}????
????/**
?????*?當(dāng)連接斷開時觸發(fā)的回調(diào)函數(shù)
?????*?@param?$connection
?????*/
????public?function?onClose($connection)
????{
????}????
????/**
?????*?當(dāng)客戶端的連接上發(fā)生錯誤時觸發(fā)
?????*?@param?$connection
?????*?@param?$code
?????*?@param?$msg
?????*/
????public?function?onError($connection,?$code,?$msg)
????{???????
?????????echo?"error?$code?$msg\n";
????}????
????/**
?????*?每個進(jìn)程啟動
?????*?@param?$worker
?????*/
????public?function?onWorkerStart($worker)
????{
????}
}
如果用tp5給出的方法,我們需要在其他的控制器中實例化該控制器類凡涩,而在我用到該框架時沒有看出該用法棒搜,誤以為實現(xiàn)功能直接在該控制器中調(diào)用即可,當(dāng)我利用到tcp鏈接時出現(xiàn)了兩個鏈接的同時調(diào)用活箕,在tp5文檔中的這個方法帮非,我無法同時實例化兩個鏈接,于是我放棄了該方法讹蘑,想要研究的同學(xué)可以繼續(xù)研究一下,下面給出我的方法筑舅。
workertest.php代碼如下:
<?php
namespace?app\push\controller;
use?Workerman\Worker;
use?Workerman\Lib\Timer;
use?Workerman\Connection\AsyncTcpConnection;
class?WorkerTest
{
????private?$connections;
????private?$connection_to_ws;
????public?function?index()
????{
????????//?$connections?=?array();?
????????$socket?=?new?Worker('websocket://0.0.0.0:2346');
????????//?設(shè)置transport開啟ssl座慰,websocket+ssl即wss
????????//?$socket->transport?=?'ssl';
????????//?啟動1個進(jìn)程對外提供服務(wù)??
????????$socket->count?=?1;
????????//給這個進(jìn)程設(shè)置一個array()
????????//?當(dāng)有客戶端連接時
????????$socket->onConnect?=?function($connection)
????????{
????????????var_dump(count($this->connections));
????????????$connection->send("lianjie");
????????????$this->connections[$connection->id]=$connection;
????????};
????????//?當(dāng)有客戶端連接時
????????$socket->onMessage?=?function($connection,$data)
????????{
????????????//?var_dump($data);
????????????//?var_dump(json_decode($data));
????????????$jdata?=?json_decode($data);
????????????if(isset($jdata->tem))
????????????{
????????????????foreach($this->connections?as?$con){
????????????????????if(isset($con->endno)&&isset($jdata->endno)&&$con->endno==$jdata->endno){
????????????????????????$con->send($jdata->tem);
????????????????????}
????????????????}
????????????}
????????????else
????????????{
????????????????$connection->send("數(shù)據(jù)已接受");
????????????????$connection->endno=$jdata->endno;
????????????????$this->connections[$connection->id]=$connection;
????????????}
????????};
????????//?當(dāng)有客戶端連接斷開時
????????$socket->onClose?=?function($connection)
????????{
????????????if(isset($connection->id))
????????????{
????????????????//?連接斷開時刪除映射
????????????????unset($this->connections[$connection->id]);
????????????}
????????};
????????$tcp?=?new?Worker('tcp://0.0.0.0:8282');
????????$tcp->onMessage?=?function($connection,?$data)
????????{
????????????if(is_null($this->connection_to_ws))
????????????{
????????????????var_dump('connect');
????????????????$this->connection_to_ws?=?new?AsyncTcpConnection('ws://119.29.170.92:2346');
????????????????$this->connection_to_ws->connect();
????????????}
????????????$this->connection_to_ws->send($data);
????????????//?var_dump(count($this->connections));
????????????//??foreach($this->connections?as?$con){
????????????//??????if($con->endno==json_decode($data)->endno){
????????????//??????????$con->send(json_decode($data)->tem);
????????????//??????}
????????????//??}
????????????};
????????//?運行worker??
????????Worker::runAll();
????????}
}
每當(dāng)前端瀏覽器通過websoket上傳給服務(wù)器對應(yīng)終端號,workerman就會將本鏈接放入到隊列翠拣,與此同時版仔,我通過tcp獲取到硬件的值,并經(jīng)過AsyncTcpConnection這個workerman對象將由tcp端口獲取的值轉(zhuǎn)發(fā)給websoket端口,再由websoket進(jìn)行遍歷當(dāng)前隊列鏈接的前端瀏覽器蛮粮,通過終端id查找并推送給對應(yīng)的前端瀏覽器益缎。