摘要:然而盡管如此,很多人可能都沒有思考過,如何優雅的寫出自己的物聯網服務器。
PHP不適合做物聯網服務端嗎?
在傳統的思維中,經常會有人告訴你,php不適合用來做物聯網服務端,讓你換java,node,go等其他語言,是的,沒錯傳統意義上的php,確實很難做物聯網服務器,因為它實在太蹩腳了,當然,這也不是意味著徹底就不能做。舉個例子,當你想實現一個TCP服務器的時候,你可能需要寫出原理大約如下的代碼:
for ($i = 0;$i <= 1;$i++){ $pid = pcntl_fork(); if($pid){ if($i == 0){ $server = stream_socket_server("tcp://127.0.0.1:9501", $errno, $errstr, STREAM_SERVER_BIND); }else if($i == 1){ $tickTime = time()+3600; while (1){ usleep(1); if($tickTime == time()){ //do my tick func } } } } }
以上代碼的意義等于在一個進程中創建一個TCP 服務端,另外一個進程中死循環來做時間檢測,從而實現定時器邏輯。這樣看起來,確實很蹩腳,而且對于編程基礎普遍比較薄弱的PHPer來說,這真的很難維護。當然這個時候,就會有人說,這不是還有Workerman嗎,是的,確實還有Workerman,Workerman就是高度封裝了上述代碼原理,幫助你專心于實現代碼邏輯的一個PHP多進程框架,因此說PHP不時候做物聯網,其實這是謬論。當然這個時候可能又會有人說,go語言有協程,你用Workerman當出現阻塞數據庫調用的時候,那效率就非常的差,很難出現高并發,這么說沒錯,但是實際上,我們可以盡可能的用多進程去彌補這個不足,也就是堆機器。當然,如果你真的想錙銖必較,沒關系,這個時候我們就可以拿出我們的殺器,那就是Swoole4.x的協程。
Swoole做TCP服務器舉個例子,如下代碼:
$server = new swoole_server("127.0.0.1", 9501); $server->on("workerstart",function ($ser,$workerId){ if($workerId == 0){ swoole_timer_tick(1000,function (){ }); } }); $server->on("connect", function ($server, $fd){ echo "connection open: {$fd} "; }); $server->on("receive", function ($server, $fd, $reactor_id, $data) { $server->send($fd, "Swoole: {$data}"); $server->close($fd); }); $server->on("close", function ($server, $fd) { echo "connection close: {$fd} "; }); $server->start();
我們就可以很快的創建出一個多進程的協程TCP服務器,而且在各個回調函數內,均自動創建協程環境,我們可以在協程回調內,去調用協程的數據庫API,這樣就避免了因為阻塞數據庫調用而導致無法處理其他客戶端請求的問題。然而盡管如此,很多人可能都沒有思考過,如何優雅的寫出自己的物聯網服務器。舉個例子,我們常見的互聯網設備管理服務中,大約可能出現如下代碼:
swoole_timer_tick(5000,function (){ $deviceList = $db->getAll(); foreach ($deviceList as $device){ //do your check /* * 例如設備狀態處于1,那么需要處理流程1 * 例如設備狀態處于2,那么需要處理流程2 * 例如設備狀態處于3,那么需要處理流程3 */ } });
定時遍歷檢查設備狀態以及廣播Actor模型
這樣乍一看好像無傷大雅,但是當出現多種設備,且每種設備邏輯都不一致的時候,那么這樣的編寫模式就很容易寫出一大坨代碼出來,而且在協程下,如果不注意變量訪問安全與協程上下文隔離,那么就很容易出現bug,導致很難維護。
什么是Actor,簡單來說,Actor就是一種高度抽象化的并發模型,每個Actor實例的內存空間都是互相隔離的,用于降低用戶編程與維護難度。關于Swoole4.x如何實現協程版本的Actor,我們之前已經在文章 https://segmentfault.com/a/11... 中講解了如何用Swoole實現協程的原理。
Actor模型庫實戰我們依舊用easyswoole/actor庫來講解,例如,我們有一種型號的設備,那么我們可以定義一個設備Actor,并把該設備的全部邏輯,寫在該actor模型內,例子代碼如下:
namespace AppDevice; use EasySwooleActorAbstractActor; use EasySwooleActorActorConfig; use EasySwooleEasySwooleLogger; use EasySwooleEasySwooleServerManager; use EasySwooleEasySwooleTrigger; class DeviceActor extends AbstractActor { private $fd; private $deviceId; private $lastHeartBeat; public static function configure(ActorConfig $actorConfig) { $actorConfig->setActorName("Device"); } protected function onStart() { $this->lastHeartBeat = time(); /* * 該參數是創建的時候傳遞的 */ $this->fd = $this->getArg()["fd"]; $this->deviceId = $this->getArg()["deviceId"]; //記錄到table manager中 DeviceManager::addDevice(new DeviceBean([ "deviceId"=>$this->deviceId, "actorId"=>$this->actorId(), "fd"=>$this->fd ])); //推送消息 ServerManager::getInstance()->getSwooleServer()->push($this->fd,"connect to server success,your actorId is {$this->actorId()}"); //創建一個定時器,如果一個設備20s沒有收到消息,自動下線 $this->tick(20*2000,function (){ if(time() - $this->lastHeartBeat > 20){ $this->exit(-1); } }); } protected function onMessage($msg) { if($msg instanceof Command){ switch ($msg->getCommand()){ case $msg::RECONNECT:{ DeviceManager::updateDeviceInfo($this->deviceId,[ "fd"=>$msg->getArg() ]); $this->fd = $msg->getArg(); Logger::getInstance()->console("deviceId {$this->deviceId} at actorId {$this->actorId()} reconnect success"); ServerManager::getInstance()->getSwooleServer()->push($this->fd,"deviceId {$this->deviceId} at actorId {$this->actorId()} reconnect success"); break; } case $msg::WS_MSG:{ $recv = $msg->getArg(); Logger::getInstance()->console("deviceId {$this->deviceId} at actorId {$this->actorId()} recv ws msg: {$recv}"); ServerManager::getInstance()->getSwooleServer()->push($this->fd,"actor recv msg for hash ".md5($recv)); break; } case $msg::REPLY_MSG:{ $recv = $msg->getArg(); Logger::getInstance()->console("deviceId {$this->deviceId} at actorId {$this->actorId()} recv reply msg: {$recv}"); ServerManager::getInstance()->getSwooleServer()->push($this->fd,"actor recv reply msg ".$recv); //此處return 一個數據,會返回給客戶端 return "actorId {$this->actorId()} recv {$recv}"; break; } } } } protected function onExit($arg) { if($arg == -1){ if(ServerManager::getInstance()->getSwooleServer()->exist($this->fd)){ ServerManager::getInstance()->getSwooleServer()->push($this->fd,"heartbeat lost,actor exit"); ServerManager::getInstance()->getSwooleServer()->close($this->fd); } } DeviceManager::deleteDevice($this->deviceId); Logger::getInstance()->console("deviceId {$this->deviceId} at actorId {$this->actorId()} exit"); } protected function onException(Throwable $throwable) { Trigger::getInstance()->throwable($throwable); } }
在該Actor內,我們定義了這個設備的生命周期行為。
設備上線,記錄設備id與fd信息,并創建心跳周期檢查
收到消息,可以對該Actor投遞數據,處理對應的消息行為
設備下線,當設備下線,可以自動的清理定時器與其他的一些通知與清理邏輯
我們可以很清楚的看到,Actor模型下,允許我們對一種設備模型進行高度自治的管理。當然,我們本章節主要在講解如何優雅的利用Swoole協程來實現Actor模型,從而更好的開發管理我們的設備,因此我不再貼過多的代碼,有興趣的同學可以在Easyswoole框架demo中查看完整的示例代碼https://github.com/easy-swool...
Easyswoole項目主頁:http://easyswoole.com/
Easyswoole github 主倉庫https://github.com/easy-swool... ,如果你覺得我們的努力有對你起到幫助作用,記得給個star
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/31668.html
摘要:新增新增模型方法,主動刷新數據表結構緩存。分布式并發模型是什么是一種與共享內存對應的并發模型,具有資源獨占性。都分布在不同的機器上。 One - 極簡 . 高性能 . 松耦合 . 分布式 . 可運行于多種環境(cli,apache/php-fpm,swoole) 碼云: https://gitee.com/vicself/onegithub: https://github.com/li...
摘要:如需了解更多物聯網網絡編程知識請點擊物聯網云端開發武器庫物聯網高并發編程之網絡編程中的線程模型值得說明的是,具體選擇線程還是進程,更多是與平臺及編程語言相關。 如需了解更多物聯網網絡編程知識請點擊:物聯網云端開發武器庫 物聯網高并發編程之網絡編程中的線程模型 值得說明的是,具體選擇線程還是進程,更多是與平臺及編程語言相關。例如 C 語言使用線程和進程都可以(例如 Nginx 使用進程...
摘要:協程與信箱得益于,我們可以基于的協程與快速實現一個信箱模式調度。樣例代碼比如在一個聊天室中,我們可以定義一個房間模型。 什么是Actor? Actor對于PHPer來說,可能會比較陌生,寫過Java的同學會比較熟悉,Java一直都有線程的概念(雖然PHP有Pthread,但不普及),它是一種非共享內存的并發模型,每個Actor內的數據獨立存在,Actor之間通過消息傳遞的形式進行交互調...
摘要:原文鏈接解決了什么問題使用模型來克服傳統面向對象編程模型的局限性,并應對高并發分布式系統所帶來的挑戰。在某些情況,這個問題可能會變得更糟糕,工作線程發生了錯誤但是其自身卻無法恢復。 這段時間由于忙畢業前前后后的事情,拖更了很久,表示非常抱歉,回歸后的第一篇文章主要是看到了Akka最新文檔中寫的What problems does the actor model solve?,閱讀完后覺...
閱讀 1084·2021-10-08 10:04
閱讀 3523·2021-08-05 10:01
閱讀 2278·2019-08-30 11:04
閱讀 1794·2019-08-29 15:29
閱讀 836·2019-08-29 15:12
閱讀 1670·2019-08-26 12:11
閱讀 3115·2019-08-26 11:33
閱讀 1163·2019-08-26 10:23