我的博客文章網址:
http://www.jloongking.cn/tp50/public/blog/index/blog?blogid=93
由于最近做一個物聯網項目,該項目需要遠程將溫度推送到服務器,并由服務器推送到web前臺,硬件可以利用tcp協議將數據上傳到到服務器,但是由于不固定ip的原因,服務器是找不到web前端的,而在這個時候我們就需要利用到長連接workerman,在之前我曾經利用workerman進行聊天軟件的編寫,實現多人在線聊天功能,而在這之前我沒有將這個博客編寫成功,本次趁著這個機會,我將長連接的知識進行了復習,正好博客完成時間沒有多久,于是將這次經歷記錄在案,以備以后自己查看學習,也方便了看到這個博文并且可能需要用到該框架的同學們。最后,我需要感謝一位同學,他在這個項目中幫助了我很多,。
在進行該教程之前,我們需要了解一下workerman這個框架和thinkphp這個框架,thinkphp框架是PHP幾大框架之一,想要了解的同學們可以到ThinkPHP5.0完全開發手冊上進行文檔的閱讀,以下簡稱tp5,tp5框架是國產為數不多的優秀框架之一,國產的哦!
而workerman則是在workerman官網上有詳細的介紹,同學們可以到該網站上進行手冊的查看。
在ThinkPHP5.0完全開發手冊里面有一篇介紹他們兩個融合的文章composer包workerman在這篇文章里介紹了tp5融合workerman的教程,但是介紹的過于簡潔,我試驗了兩次并沒有走通,而在這次的記錄中我將我的融合過程和這個方法進行對比,分析以前沒有走通的原因。
不論在哪個方法中,我們都需要將wokerman的包引入,我們需要用到composer,沒有安裝composer的同學需要自行安裝。
第一步
導入workerman包:
composer?require?topthink/think-worker
如果windows服務器還要利用以下命令:
composer?require?workerman/workerman-for-win
如運行出現錯誤PHP Fatal error: Call to undefined function Workerman\Lib\pcntl_signal(),需要刪除vendor\workerman\workerman,防止命名覆蓋。
當包引入完成后,我們會在項目根目錄下的vendor文件夾下看到workerman文件夾,這樣框架就引入了該項目,我們下一步需要配置服務的啟動和引用。
第二步
在這一步中我們需要將啟動服務文件放入到項目根目錄中,在根目錄中我們新建啟動服務文件server.php
代碼如下:
#!/usr/bin/env?php
<?php
define('APP_PATH',?__DIR__?.?'/application/');
//這是原來的代碼
//define('BIND_MODULE','push/Worker');
//這是我修改后的代碼
define('BIND_MODULE','push/Workertest/index');
//?加載框架引導文件
require?__DIR__?.?'/thinkphp/start.php';
這個代碼的意思是綁定workerman的模塊是/application/push/Worker這個控制器,但是由于我們沒有用tp5這個框架的引用方式這里我們將代碼改為如上所示,直接進入到控制器的index方法,在下一步我們會介紹為什么用這種方法。我們在下一步中就會定義模塊,也就是實現功能的地方:/application/push/workertest.php
第三步
在這一步開始前我們來看一下tp5開發手冊中的worker.php,需要聲明的是我并沒用利用該方法。
????一共有一個變量和5個方法,變量是定義端口和域名的,方法是分別為,連接上時,服務開始時,接到信息時,錯誤時,斷開時的相應處理方法。
代碼如下
<?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('我收到你的信息了');
????}????
????/**
?????*?當連接建立時觸發的回調函數
?????*?@param?$connection
?????*/
????public?function?onConnect($connection)
????{
????}????
????/**
?????*?當連接斷開時觸發的回調函數
?????*?@param?$connection
?????*/
????public?function?onClose($connection)
????{
????}????
????/**
?????*?當客戶端的連接上發生錯誤時觸發
?????*?@param?$connection
?????*?@param?$code
?????*?@param?$msg
?????*/
????public?function?onError($connection,?$code,?$msg)
????{???????
?????????echo?"error?$code?$msg\n";
????}????
????/**
?????*?每個進程啟動
?????*?@param?$worker
?????*/
????public?function?onWorkerStart($worker)
????{
????}
}
如果用tp5給出的方法,我們需要在其他的控制器中實例化該控制器類,而在我用到該框架時沒有看出該用法,誤以為實現功能直接在該控制器中調用即可,當我利用到tcp鏈接時出現了兩個鏈接的同時調用,在tp5文檔中的這個方法,我無法同時實例化兩個鏈接,于是我放棄了該方法,想要研究的同學可以繼續研究一下,下面給出我的方法。
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');
????????//?設置transport開啟ssl,websocket+ssl即wss
????????//?$socket->transport?=?'ssl';
????????//?啟動1個進程對外提供服務??
????????$socket->count?=?1;
????????//給這個進程設置一個array()
????????//?當有客戶端連接時
????????$socket->onConnect?=?function($connection)
????????{
????????????var_dump(count($this->connections));
????????????$connection->send("lianjie");
????????????$this->connections[$connection->id]=$connection;
????????};
????????//?當有客戶端連接時
????????$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("數據已接受");
????????????????$connection->endno=$jdata->endno;
????????????????$this->connections[$connection->id]=$connection;
????????????}
????????};
????????//?當有客戶端連接斷開時
????????$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();
????????}
}
每當前端瀏覽器通過websoket上傳給服務器對應終端號,workerman就會將本鏈接放入到隊列,與此同時,我通過tcp獲取到硬件的值,并經過AsyncTcpConnection這個workerman對象將由tcp端口獲取的值轉發給websoket端口,再由websoket進行遍歷當前隊列鏈接的前端瀏覽器,通過終端id查找并推送給對應的前端瀏覽器。