一起學(xué)習(xí)Swoole的簡(jiǎn)單運(yùn)用實(shí)現(xiàn)例子

一起學(xué)習(xí)Swoole的簡(jiǎn)單運(yùn)用實(shí)現(xiàn)例子

前言

我們使用php開(kāi)發(fā)web應(yīng)用基本都是使用傳統(tǒng)的lamp/lnmp模式來(lái)提供http服務(wù),這種模式一般是同步且堵塞的,若我們想使用php開(kāi)發(fā)一些高級(jí)的特性(例如:異步,非堵塞,網(wǎng)絡(luò)服務(wù)器等),那么swoole無(wú)疑是最佳的選擇,那什么是swoole呢?

PHP的異步、并行、高性能網(wǎng)絡(luò)通信引擎,使用純c語(yǔ)言編寫(xiě),提供了 PHP語(yǔ)言的異步線程服務(wù)器, 異步TCP/udp網(wǎng)絡(luò)客戶端, 異步mysql, 異步redis數(shù)據(jù)庫(kù)連接池, AsyncTask, 消息隊(duì)列, 毫秒定時(shí)器, 異步文件讀寫(xiě), 異步DNS查詢。 Swoole內(nèi)置了 Http/websocket服務(wù)器端/ 客戶端、 Http2.0服務(wù)器端/ 客戶端。

簡(jiǎn)單的來(lái)說(shuō),Swoole是一個(gè)PHP擴(kuò)展,實(shí)現(xiàn)了網(wǎng)絡(luò)層的很多功能,應(yīng)用場(chǎng)景非常廣,下面列舉幾個(gè)例子簡(jiǎn)單介紹一下Swoole的應(yīng)用。

推薦(免費(fèi)):swoole

安裝

按照官方文檔進(jìn)行安裝:Swoole官網(wǎng),安裝完后使用命令:

php?-m

查看是否安裝成功。注意:Swoole從2.0版本開(kāi)始支持了內(nèi)置協(xié)程,需使用PHP7。

基于TCP的郵件服務(wù)器

使用Swoole提供TCP服務(wù),異步任務(wù)發(fā)送郵件。

郵件功能:

PHPMailer

PHP主代碼:

<?php $object = new MailServer();  $setting = [     &#39;log_file&#39; =>?'swoole.log', ????'worker_num'?=&gt;?4,?//?4個(gè)工作進(jìn)程 ????'task_worker_num'?=&gt;?10,?//?10個(gè)任務(wù)進(jìn)程 ]; $server?=?new?swoole_server("127.0.0.1",?9501); $server-&gt;set($setting);  $server-&gt;on('WorkerStart',?array($object,?'onWorkerStart')); $server-&gt;on('Connect',?array($object,?'onConnect')); $server-&gt;on('Receive',?array($object,?'onReceive')); $server-&gt;on('Close',?array($object,?'onClose')); $server-&gt;on('Task',?array($object,?'onTask')); $server-&gt;on('Finish',?array($object,?'onFinish'));  $server-&gt;start();  class?MailServer { ????/**?@var?Mail?*/ ????private?$handle;  ????public?function?__construct() ????{ ????????require?'Mail.php';?//?PHPMailer郵件服務(wù)類(lèi) ????}  ????public?function?onWorkerStart($server,?$workerId) ????{ ????????$mailConfig?=?require?'MailConfig.php';?//?發(fā)件人信息,重啟時(shí)會(huì)重新加載配置文件 ????????$this-&gt;handle?=?new?Mail($mailConfig); ????}  ????public?function?onConnect($server,?$fd,?$reactorId) ????{  ????}  ????public?function?onReceive($server,?$fd,?$reactorId,?$data) ????{ ????????$return?=?[]; ????????$dataArr?=?JSon_decode($data,?true); ????????if?(empty($dataArr)?||?empty($dataArr['address'])?||?empty($dataArr['subject'])?||?empty($dataArr['body']))?{ ????????????$return['code']?=?-1; ????????????$return['msg']?=?'參數(shù)不能為空'; ????????}?else?{?//?參數(shù)校驗(yàn)成功 ????????????$server-&gt;task($data);?//?投遞一個(gè)任務(wù) ????????????$return['code']?=?0; ????????????$return['msg']?=?'投遞任務(wù)成功'; ????????} ????????$server-&gt;send($fd,?json_encode($return)); ????}  ????public?function?onTask($server,?$taskId,?$workerId,?$data) ????{ ????????$data?=?json_decode($data,?true); ????????$this-&gt;handle-&gt;send($data['address'],?$data['subject'],?$data['body']);?//?發(fā)送郵件 ????}  ????public?function?onFinish($server,?$task_id,?$data) ????{  ????}  ????public?function?onClose($server,?$fd,?$reactorId) ????{  ????} }

發(fā)件人信息配置:

<?php // 郵件發(fā)送人信息配置  return [     &#39;host&#39; =>?'smtp.qq.com', ????'port'?=&gt;?'465', ????'fromName'?=&gt;?'Mr.litt', ????'username'?=&gt;?'137057181@qq.com', ????'password'?=&gt;?'', ];

PHPMailer郵件服務(wù)類(lèi):

<?php require &#39;vendor/phpmailer/phpmailer/src/Exception.php&#39;; require &#39;vendor/phpmailer/phpmailer/src/PHPMailer.php&#39;; require &#39;vendor/phpmailer/phpmailer/src/SMTP.php&#39;;  use PHPMailerPHPMailerPHPMailer;  class Mail {     private $host;     private $port;     private $fromName;     private $username;     private $password;      public function __construct($config)     {         !empty($config[&#39;host&#39;]) && $this->host?=?$config['host']; ????????!empty($config['port'])?&amp;&amp;?$this-&gt;port?=?$config['port']; ????????!empty($config['fromName'])?&amp;&amp;?$this-&gt;fromName?=?$config['fromName']; ????????!empty($config['username'])?&amp;&amp;?$this-&gt;username?=?$config['username']; ????????!empty($config['password'])?&amp;&amp;?$this-&gt;password?=?$config['password']; ????????if?(empty($this-&gt;host)?||?empty($this-&gt;port)?||?empty($this-&gt;fromName)?|| ????????????empty($this-&gt;username)?||?empty($this-&gt;password))?{ ????????????throw?new?Exception('發(fā)件人信息錯(cuò)誤'); ????????} ????}  ????public?function?send($address,?$subject,?$body) ????{ ????????if?(empty($address)?||?empty($subject)?||?empty($body))?{ ????????????throw?new?Exception('收件人信息錯(cuò)誤'); ????????} ????????//?實(shí)例化PHPMailer核心類(lèi) ????????$mail?=?new?PHPMailer(); ????????//?是否啟用smtp的debug進(jìn)行調(diào)試?開(kāi)發(fā)環(huán)境建議開(kāi)啟?生產(chǎn)環(huán)境注釋掉即可?默認(rèn)關(guān)閉debug調(diào)試模式 ????????$mail-&gt;SMTPDebug?=?0; ????????//?使用smtp鑒權(quán)方式發(fā)送郵件 ????????$mail-&gt;isSMTP(); ????????//?smtp需要鑒權(quán)?這個(gè)必須是true ????????$mail-&gt;SMTPAuth?=?true; ????????//?鏈接郵箱的服務(wù)器地址 ????????$mail-&gt;Host?=?$this-&gt;host; ????????//?設(shè)置使用ssl加密方式登錄鑒權(quán) ????????$mail-&gt;SMTPSecure?=?'ssl'; ????????//?設(shè)置ssl連接smtp服務(wù)器的遠(yuǎn)程服務(wù)器端口號(hào) ????????$mail-&gt;Port?=?$this-&gt;port; ????????//?設(shè)置發(fā)送的郵件的編碼 ????????$mail-&gt;CharSet?=?'UTF-8'; ????????//?設(shè)置發(fā)件人昵稱(chēng)?顯示在收件人郵件的發(fā)件人郵箱地址前的發(fā)件人姓名 ????????$mail-&gt;FromName?=?$this-&gt;fromName; ????????//?smtp登錄的賬號(hào)?QQ郵箱即可 ????????$mail-&gt;Username?=?$this-&gt;username; ????????//?smtp登錄的密碼?使用生成的授權(quán)碼 ????????$mail-&gt;Password?=?$this-&gt;password; ????????//?設(shè)置發(fā)件人郵箱地址?同登錄賬號(hào) ????????$mail-&gt;From?=?$this-&gt;username; ????????//?郵件正文是否為html編碼?注意此處是一個(gè)方法 ????????$mail-&gt;isHTML(true); ????????//?設(shè)置收件人郵箱地址 ????????$mail-&gt;addAddress($address); ????????//?添加多個(gè)收件人?則多次調(diào)用方法即可 ????????//$mail-&gt;addAddress('87654321@163.com'); ????????//?添加該郵件的主題 ????????$mail-&gt;Subject?=?$subject; ????????//?添加郵件正文 ????????$mail-&gt;Body?=?$body; ????????//?為該郵件添加附件 ????????//$mail-&gt;addAttachment('./example.pdf'); ????????//?發(fā)送郵件?返回狀態(tài) ????????$status?=?$mail-&gt;send(); ????????return?$status; ????} }

注意事項(xiàng):

  1. 修改發(fā)件人信息后,只需重啟task_worker就生效,命令 kill -USER1 主進(jìn)程PID。
  2. TCP客戶端可使用swoole_client類(lèi)來(lái)模擬。
  3. 短信、推送等異步任務(wù)同樣適用于此場(chǎng)景。

?

基于WebSocket多房間聊天功能

使用Swoole提供WebSocket服務(wù),使用Redis保存房間人員信息。

PHP主代碼:

<?php $object = new ChatServer();  $setting = [     &#39;log_file&#39; =>?'swoole_ws.log', ????'worker_num'?=&gt;?4,?//?4個(gè)工作進(jìn)程 ]; $ws?=?new?swoole_websocket_server("127.0.0.1",?9502); $ws-&gt;set($setting);  $ws-&gt;on('WorkerStart',?array($object,?'onWorkerStart')); $ws-&gt;on('open',?array($object,?'onOpen')); $ws-&gt;on('message',?array($object,?'onMessage')); $ws-&gt;on('close',?array($object,?'onClose'));  $ws-&gt;start();  class?ChatServer { ????/**?@var??Redis?*/ ????private?$redis;  ????public?function?__construct() ????{ ????????echo?"啟動(dòng)前清理數(shù)據(jù) "; ????????$redis?=?new?Redis(); ????????$redis-&gt;connect('127.0.0.1',?6379); ????????if?($redis-&gt;ping()?!=?'+PONG')?{ ????????????echo?"redis連接失敗 ";exit; ????????} ????????$delKeys?=?$redis-&gt;keys('fd_*'); ????????foreach?($delKeys?as?$key)?{ ????????????$redis-&gt;del($key); ????????} ????????$delKeys?=?$redis-&gt;keys('roomId_*'); ????????foreach?($delKeys?as?$key)?{ ????????????$redis-&gt;del($key); ????????} ????}  ????public?function?onWorkerStart($ws,?$workerId) ????{ ????????$redis?=?new?Redis(); ????????$redis-&gt;connect('127.0.0.1',?6379); ????????if?($redis-&gt;ping()?!=?'+PONG')?{ ????????????echo?"redis連接失敗 "; ????????} ????????$this-&gt;redis?=?$redis; ????}  ????public?function?onOpen($ws,?$request) ????{ ????????echo?"fd:{$request-&gt;fd}?is?open "; ????????if?(empty($request-&gt;get['roomId'])?||?empty($request-&gt;get['nick']))?{ ????????????$status?=?'fail'; ????????}?else?{ ????????????//建立身份關(guān)聯(lián) ????????????$this-&gt;redis-&gt;hSet("fd_".$request-&gt;fd,?'roomId',?$request-&gt;get['roomId']); ????????????$this-&gt;redis-&gt;hSet("fd_".$request-&gt;fd,?'nick',?$request-&gt;get['nick']); ????????????$this-&gt;redis-&gt;sAdd("roomId_".$request-&gt;get['roomId'],?$request-&gt;fd);  ????????????$status?=?'success'; ????????} ????????$sendData?=?[ ????????????'cmd'?=&gt;?'open', ????????????'data'?=&gt;?[ ????????????????'status'?=&gt;?$status ????????????] ????????]; ????????$ws-&gt;push($request-&gt;fd,?json_encode($sendData)); ????}  ????public?function?onMessage($ws,?$frame) ????{ ????????echo?"fd:[$frame-&gt;fd},?Message:?{$frame-&gt;data} "; ????????if?(!empty($frame-&gt;data))?{ ????????????$fdInfo?=?$this-&gt;redis-&gt;hGetAll("fd_".$frame-&gt;fd); ????????????if?(!empty($fdInfo['nick'])?&amp;&amp;?!empty($fdInfo['roomId']))?{ ????????????????$sendData?=?[ ????????????????????'cmd'?=&gt;?'ReceiveMessage', ????????????????????'data'?=&gt;?[ ????????????????????????'nick'?=&gt;?$fdInfo['nick'], ????????????????????????'msg'?=&gt;?$frame-&gt;data, ????????????????????] ????????????????]; ????????????????$fdArr?=?$this-&gt;redis-&gt;sMembers("roomId_".$fdInfo['roomId']); ????????????????foreach?($fdArr?as?$fd)?{ ????????????????????$ws-&gt;push($fd,?json_encode($sendData)); ????????????????} ????????????} ????????} ????}  ????public?function?onClose($ws,?$fd,?$reactorId) ????{ ????????echo?"fd:{$fd}?is?closed "; ????????//刪除fd身份數(shù)據(jù)并在房間內(nèi)移動(dòng)該fd ????????$fdInfo?=?$this-&gt;redis-&gt;hGetAll("fd_".$fd); ????????if?(!empty($fdInfo['roomId']))?{ ????????????$this-&gt;redis-&gt;sRem("roomId_".$fdInfo['roomId'],?$fd); ????????} ????????$this-&gt;redis-&gt;del("fd_".$fd); ????} }

注意事項(xiàng):

1.Worker進(jìn)程之間不能共享變量,這里使用Redis來(lái)共享數(shù)據(jù)。

2.Worker進(jìn)程不能共用同一個(gè)Redis客戶端,需要放到onWorkerStart中實(shí)例化。

3.客戶端可使用JS內(nèi)置等WebSokcet客戶端,異步的PHP程序可使用SwooleHttpClient,同步可以使用swoole/framework提供的同步WebSocket客戶端。

?

基于HTTP的簡(jiǎn)易框架

使用Swoole提供HTTP服務(wù),模擬官方Swoole框架實(shí)現(xiàn)一個(gè)簡(jiǎn)易框架。

PHP主代碼:

<?php $object = new AppServer();  $setting = [     &#39;log_file&#39; =>?'swoole_http.log', ????'worker_num'?=&gt;?4,?//?4個(gè)工作進(jìn)程 ]; $server?=?new?swoole_http_server("127.0.0.1",?9503); $server-&gt;set($setting);  $server-&gt;on('request',?array($object,?'onRequest')); $server-&gt;on('close',?array($object,?'onClose'));  $server-&gt;start();  /** ?*?Class?AppServer ?*?@property?swoole_http_request?$request ?*?@property?swoole_http_response?$response ?*?@property?PDO?$db ?*?@property?libSession?$session ?*/ class?AppServer { ????private?$module?=?[];  ????/**?@var?AppServer?*/ ????private?static?$instance;  ????public?static?function?getInstance() ????{ ????????return?self::$instance; ????}  ????public?function?__construct() ????{ ????????$baseControllerFile?=?__DIR__?.'/controller/Base.php'; ????????require_once?"$baseControllerFile"; ????}  ????/** ?????*?@param?swoole_http_request?$request ?????*?@param?swoole_http_response?$response ?????*/ ????public?function?onRequest($request,?$response) ????{ ????????$this-&gt;module['request']?=?$request; ????????$this-&gt;module['response']?=?$response; ????????self::$instance?=?$this;  ????????list($controllerName,?$methodName)?=?$this-&gt;route($request); ????????empty($controllerName)?&amp;&amp;?$controllerName?=?'index'; ????????empty($methodName)?&amp;&amp;?$methodName?=?'index';  ????????try?{ ????????????$controllerClass?=?"controller"?.?ucfirst($controllerName); ????????????$controllerFile?=?__DIR__?.?"/controller/"?.?ucfirst($controllerName)?.?".php"; ????????????if?(!class_exists($controllerClass,?false))?{ ????????????????if?(!is_file($controllerFile))?{ ????????????????????throw?new?Exception('控制器不存在'); ????????????????} ????????????????require_once?"$controllerFile"; ????????????}  ????????????$controller?=?new?$controllerClass($this); ????????????if?(!method_exists($controller,?$methodName))?{ ????????????????throw?new?Exception('控制器方法不存在'); ????????????}  ????????????ob_start(); ????????????$return?=?$controller-&gt;$methodName(); ????????????$return?.=?ob_get_contents(); ????????????ob_end_clean(); ????????????$this-&gt;session-&gt;end(); ????????????$response-&gt;end($return); ????????}?catch?(Exception?$e)?{ ????????????$response-&gt;status(500); ????????????$response-&gt;end($e-&gt;getMessage()); ????????} ????}  ????private?function?route($request) ????{ ????????$pathInfo?=?explode('/',?$request-&gt;server['path_info']); ????????return?[$pathInfo[1],?$pathInfo[2]]; ????}  ????public?function?onClose($server,?$fd,?$reactorId) ????{  ????}  ????public?function?__get($name) ????{ ????????if?(!in_array($name,?array('request',?'response',?'db',?'session')))?{ ????????????return?null; ????????} ????????if?(empty($this-&gt;module[$name]))?{ ????????????$moduleClass?=?"lib"?.?ucfirst($name); ????????????$moduleFile?=?__DIR__?.?'/lib/'?.?ucfirst($name)?.?".php"; ????????????if?(is_file($moduleFile))?{ ????????????????require_once?"$moduleFile"; ????????????????$object?=?new?$moduleClass; ????????????????$this-&gt;module[$name]?=?$object; ????????????} ????????} ????????return?$this-&gt;module[$name]; ????} }

使用header和setCooike示例:

<?php namespace controller;  class Http extends Base {     public function header()     {         //發(fā)送Http狀態(tài)碼,如500, 404等等         $this->response-&gt;status(302); ????????//使用此函數(shù)代替PHP的header函數(shù) ????????$this-&gt;response-&gt;header('Location',?'http://www.baidu.com/'); ????}  ????public?function?cookie() ????{ ????????$this-&gt;response-&gt;cookie('http_cookie','http_cookie_value'); ????} }

Session實(shí)現(xiàn):

<?php namespace lib;  class Session {     private $sessionId;     private $cookieKey;     private $storeDir;     private $file;     private $isStart;      public function __construct()     {         $this->cookieKey?=?'PHPSESSID'; ????????$this-&gt;storeDir?=?'tmp/'; ????????$this-&gt;isStart?=?false; ????}  ????public?function?start() ????{ ????????$this-&gt;isStart?=?true; ????????$appServer?=?AppServer::getInstance(); ????????$request?=?$appServer-&gt;request; ????????$response?=?$appServer-&gt;response; ????????$sessionId?=?$request-&gt;cookie[$this-&gt;cookieKey]; ????????if?(empty($sessionId)){ ????????????$sessionId?=?uniqid(); ????????????$response-&gt;cookie($this-&gt;cookieKey,?$sessionId); ????????} ????????$this-&gt;sessionId?=?$sessionId; ????????$storeFile?=?$this-&gt;storeDir?.?$sessionId; ????????if?(!is_file($storeFile))?{ ????????????touch($storeFile); ????????} ????????$session?=?$this-&gt;get($storeFile); ????????$_SESSION?=?$session; ????}  ????public?function?end() ????{ ????????$this-&gt;save(); ????}  ????public?function?commit() ????{ ????????$this-&gt;save(); ????}  ????private?function?save() ????{ ????????if?($this-&gt;isStart)?{ ????????????$data?=?json_encode($_SESSION); ????????????ftruncate($this-&gt;file,?0);  ????????????if?($data)?{ ????????????????rewind($this-&gt;file); ????????????????fwrite($this-&gt;file,?$data); ????????????} ????????????flock($this-&gt;file,?LOCK_UN); ????????????fclose($this-&gt;file); ????????} ????}  ????private?function?get($fileName) ????{ ????????$this-&gt;file?=?fopen($fileName,?'c+b'); ????????if(flock($this-&gt;file,?LOCK_EX?|?LOCK_NB))?{ ????????????$data?=?[]; ????????????clearstatcache(); ????????????if?(filesize($fileName)?&gt;?0)?{ ????????????????$data?=?fread($this-&gt;file,?filesize($fileName)); ????????????????$data?=?json_decode($data,?true); ????????????} ????????????return?$data; ????????} ????} }

注意事項(xiàng):

  1. 使用Redis/MySQL等客戶端理應(yīng)使用線程池,參照官方Swoole框架。
  2. Swoole是在執(zhí)行PHP文件這一階段進(jìn)行接管,因此header,setCooike,seesion_start不可用,header和setCooike可用$response變量實(shí)現(xiàn),Session可自行實(shí)現(xiàn)。
  3. 推薦查看官方框架:Swoole框架。

總結(jié)

Swoole的應(yīng)用遠(yuǎn)不如此,Swoole就像打開(kāi)了PHP世界的新大門(mén),我覺(jué)得每一位PHPer都應(yīng)該學(xué)習(xí)和掌握Swoole的用法,能學(xué)到很多使用LAMP/LNMP模式時(shí)未涉及到的知識(shí)點(diǎn)。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊13 分享