Swoole框架欄目介紹介紹Swoole的簡單運用實現(xiàn)例子
推薦(免費):Swoole框架
前言
我們使用PHP開發(fā)WEB應用基本都是使用傳統(tǒng)的LAMP/LNMP模式來提供HTTP服務,這種模式一般是同步且堵塞的,若我們想使用PHP開發(fā)一些高級的特性(例如:異步,非堵塞,網絡服務器等),那么Swoole無疑是最佳的選擇,那什么是Swoole呢?
PHP的異步、并行、高性能網絡通信引擎,使用純C語言編寫,提供了 PHP語言的異步多線程服務器, 異步TCP/UDP網絡客戶端, 異步MySQL, 異步Redis, 數(shù)據(jù)庫連接池, AsyncTask, 消息隊列, 毫秒定時器, 異步文件讀寫, 異步DNS查詢。 Swoole內置了 Http/WebSocket服務器端/ 客戶端、 Http2.0服務器端/ 客戶端。
簡單的來說,Swoole是一個PHP擴展,實現(xiàn)了網絡層的很多功能,應用場景非常廣,下面列舉幾個例子簡單介紹一下Swoole的應用。
安裝
按照官方文檔進行安裝:Swoole官網,安裝完后使用命令:
php -m
查看是否安裝成功。注意:Swoole從2.0版本開始支持了內置協(xié)程,需使用PHP7。
基于TCP的郵件服務器
使用Swoole提供TCP服務,異步任務發(fā)送郵件。
郵件功能:
PHPMailer
PHP主代碼:
<?php $object = new MailServer(); $setting = [ 'log_file' => 'swoole.log', 'worker_num' => 4, // 4個工作進程 'task_worker_num' => 10, // 10個任務進程 ]; $server = new swoole_server("127.0.0.1", 9501); $server->set($setting); $server->on('WorkerStart', array($object, 'onWorkerStart')); $server->on('Connect', array($object, 'onConnect')); $server->on('Receive', array($object, 'onReceive')); $server->on('Close', array($object, 'onClose')); $server->on('Task', array($object, 'onTask')); $server->on('Finish', array($object, 'onFinish')); $server->start(); class MailServer { /** @var Mail */ private $handle; public function __construct() { require 'Mail.php'; // PHPMailer郵件服務類 } public function onWorkerStart($server, $workerId) { $mailConfig = require 'MailConfig.php'; // 發(fā)件人信息,重啟時會重新加載配置文件 $this->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ù)校驗成功 $server->task($data); // 投遞一個任務 $return['code'] = 0; $return['msg'] = '投遞任務成功'; } $server->send($fd, json_encode($return)); } public function onTask($server, $taskId, $workerId, $data) { $data = json_decode($data, true); $this->handle->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 [ 'host' => 'smtp.qq.com', 'port' => '465', 'fromName' => 'Mr.litt', 'username' => '137057181@qq.com', 'password' => '', ];
PHPMailer郵件服務類:
<?php require 'vendor/phpmailer/phpmailer/src/Exception.php'; require 'vendor/phpmailer/phpmailer/src/PHPMailer.php'; require 'vendor/phpmailer/phpmailer/src/SMTP.php'; use PHPMailer\PHPMailer\PHPMailer; class Mail { private $host; private $port; private $fromName; private $username; private $password; public function __construct($config) { !empty($config['host']) && $this->host = $config['host']; !empty($config['port']) && $this->port = $config['port']; !empty($config['fromName']) && $this->fromName = $config['fromName']; !empty($config['username']) && $this->username = $config['username']; !empty($config['password']) && $this->password = $config['password']; if (empty($this->host) || empty($this->port) || empty($this->fromName) || empty($this->username) || empty($this->password)) { throw new Exception('發(fā)件人信息錯誤'); } } public function send($address, $subject, $body) { if (empty($address) || empty($subject) || empty($body)) { throw new Exception('收件人信息錯誤'); } // 實例化PHPMailer核心類 $mail = new PHPMailer(); // 是否啟用smtp的debug進行調試 開發(fā)環(huán)境建議開啟 生產環(huán)境注釋掉即可 默認關閉debug調試模式 $mail->SMTPDebug = 0; // 使用smtp鑒權方式發(fā)送郵件 $mail->isSMTP(); // smtp需要鑒權 這個必須是true $mail->SMTPAuth = true; // 鏈接郵箱的服務器地址 $mail->Host = $this->host; // 設置使用ssl加密方式登錄鑒權 $mail->SMTPSecure = 'ssl'; // 設置ssl連接smtp服務器的遠程服務器端口號 $mail->Port = $this->port; // 設置發(fā)送的郵件的編碼 $mail->CharSet = 'UTF-8'; // 設置發(fā)件人昵稱 顯示在收件人郵件的發(fā)件人郵箱地址前的發(fā)件人姓名 $mail->FromName = $this->fromName; // smtp登錄的賬號 QQ郵箱即可 $mail->Username = $this->username; // smtp登錄的密碼 使用生成的授權碼 $mail->Password = $this->password; // 設置發(fā)件人郵箱地址 同登錄賬號 $mail->From = $this->username; // 郵件正文是否為html編碼 注意此處是一個方法 $mail->isHTML(true); // 設置收件人郵箱地址 $mail->addAddress($address); // 添加多個收件人 則多次調用方法即可 //$mail->addAddress('87654321@163.com'); // 添加該郵件的主題 $mail->Subject = $subject; // 添加郵件正文 $mail->Body = $body; // 為該郵件添加附件 //$mail->addAttachment('./example.pdf'); // 發(fā)送郵件 返回狀態(tài) $status = $mail->send(); return $status; } }
注意事項:
- 修改發(fā)件人信息后,只需重啟task_worker就生效,命令 kill -USER1 主進程PID。
- TCP客戶端可使用swoole_client類來模擬。
- 短信、推送等異步任務同樣適用于此場景。
基于WebSocket多房間聊天功能
使用Swoole提供WebSocket服務,使用Redis保存房間人員信息。
PHP主代碼:
<?php $object = new ChatServer(); $setting = [ 'log_file' => 'swoole_ws.log', 'worker_num' => 4, // 4個工作進程 ]; $ws = new swoole_websocket_server("127.0.0.1", 9502); $ws->set($setting); $ws->on('WorkerStart', array($object, 'onWorkerStart')); $ws->on('open', array($object, 'onOpen')); $ws->on('message', array($object, 'onMessage')); $ws->on('close', array($object, 'onClose')); $ws->start(); class ChatServer { /** @var Redis */ private $redis; public function __construct() { echo "啟動前清理數(shù)據(jù)\n"; $redis = new Redis(); $redis->connect('127.0.0.1', 6379); if ($redis->ping() != '+PONG') { echo "redis連接失敗\n";exit; } $delKeys = $redis->keys('fd_*'); foreach ($delKeys as $key) { $redis->del($key); } $delKeys = $redis->keys('roomId_*'); foreach ($delKeys as $key) { $redis->del($key); } } public function onWorkerStart($ws, $workerId) { $redis = new Redis(); $redis->connect('127.0.0.1', 6379); if ($redis->ping() != '+PONG') { echo "redis連接失敗\n"; } $this->redis = $redis; } public function onOpen($ws, $request) { echo "fd:{$request->fd} is open\n"; if (empty($request->get['roomId']) || empty($request->get['nick'])) { $status = 'fail'; } else { //建立身份關聯(lián) $this->redis->hSet("fd_".$request->fd, 'roomId', $request->get['roomId']); $this->redis->hSet("fd_".$request->fd, 'nick', $request->get['nick']); $this->redis->sAdd("roomId_".$request->get['roomId'], $request->fd); $status = 'success'; } $sendData = [ 'cmd' => 'open', 'data' => [ 'status' => $status ] ]; $ws->push($request->fd, json_encode($sendData)); } public function onMessage($ws, $frame) { echo "fd:[$frame->fd}, Message: {$frame->data}\n"; if (!empty($frame->data)) { $fdInfo = $this->redis->hGetAll("fd_".$frame->fd); if (!empty($fdInfo['nick']) && !empty($fdInfo['roomId'])) { $sendData = [ 'cmd' => 'ReceiveMessage', 'data' => [ 'nick' => $fdInfo['nick'], 'msg' => $frame->data, ] ]; $fdArr = $this->redis->sMembers("roomId_".$fdInfo['roomId']); foreach ($fdArr as $fd) { $ws->push($fd, json_encode($sendData)); } } } } public function onClose($ws, $fd, $reactorId) { echo "fd:{$fd} is closed\n"; //刪除fd身份數(shù)據(jù)并在房間內移動該fd $fdInfo = $this->redis->hGetAll("fd_".$fd); if (!empty($fdInfo['roomId'])) { $this->redis->sRem("roomId_".$fdInfo['roomId'], $fd); } $this->redis->del("fd_".$fd); } }
注意事項:
1.Worker進程之間不能共享變量,這里使用Redis來共享數(shù)據(jù)。
2.Worker進程不能共用同一個Redis客戶端,需要放到onWorkerStart中實例化。
3.客戶端可使用JS內置等WebSokcet客戶端,異步的PHP程序可使用Swoole\Http\Client,同步可以使用swoole/framework提供的同步WebSocket客戶端。
基于HTTP的簡易框架
使用Swoole提供HTTP服務,模擬官方Swoole框架實現(xiàn)一個簡易框架。
PHP主代碼:
<?php $object = new AppServer(); $setting = [ 'log_file' => 'swoole_http.log', 'worker_num' => 4, // 4個工作進程 ]; $server = new swoole_http_server("127.0.0.1", 9503); $server->set($setting); $server->on('request', array($object, 'onRequest')); $server->on('close', array($object, 'onClose')); $server->start(); /** * Class AppServer * @property \swoole_http_request $request * @property \swoole_http_response $response * @property \PDO $db * @property \lib\Session $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->module['request'] = $request; $this->module['response'] = $response; self::$instance = $this; list($controllerName, $methodName) = $this->route($request); empty($controllerName) && $controllerName = 'index'; empty($methodName) && $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->$methodName(); $return .= ob_get_contents(); ob_end_clean(); $this->session->end(); $response->end($return); } catch (Exception $e) { $response->status(500); $response->end($e->getMessage()); } } private function route($request) { $pathInfo = explode('/', $request->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->module[$name])) { $moduleClass = "\\lib\\" . ucfirst($name); $moduleFile = __DIR__ . '/lib/' . ucfirst($name) . ".php"; if (is_file($moduleFile)) { require_once "$moduleFile"; $object = new $moduleClass; $this->module[$name] = $object; } } return $this->module[$name]; } }
使用header和setCooike示例:
<?php namespace controller; class Http extends Base { public function header() { //發(fā)送Http狀態(tài)碼,如500, 404等等 $this->response->status(302); //使用此函數(shù)代替PHP的header函數(shù) $this->response->header('Location', 'http://www.baidu.com/'); } public function cookie() { $this->response->cookie('http_cookie','http_cookie_value'); } }
Session實現(xiàn):
<?php namespace lib; class Session { private $sessionId; private $cookieKey; private $storeDir; private $file; private $isStart; public function __construct() { $this->cookieKey = 'PHPSESSID'; $this->storeDir = 'tmp/'; $this->isStart = false; } public function start() { $this->isStart = true; $appServer = \AppServer::getInstance(); $request = $appServer->request; $response = $appServer->response; $sessionId = $request->cookie[$this->cookieKey]; if (empty($sessionId)){ $sessionId = uniqid(); $response->cookie($this->cookieKey, $sessionId); } $this->sessionId = $sessionId; $storeFile = $this->storeDir . $sessionId; if (!is_file($storeFile)) { touch($storeFile); } $session = $this->get($storeFile); $_SESSION = $session; } public function end() { $this->save(); } public function commit() { $this->save(); } private function save() { if ($this->isStart) { $data = json_encode($_SESSION); ftruncate($this->file, 0); if ($data) { rewind($this->file); fwrite($this->file, $data); } flock($this->file, LOCK_UN); fclose($this->file); } } private function get($fileName) { $this->file = fopen($fileName, 'c+b'); if(flock($this->file, LOCK_EX | LOCK_NB)) { $data = []; clearstatcache(); if (filesize($fileName) > 0) { $data = fread($this->file, filesize($fileName)); $data = json_decode($data, true); } return $data; } } }
注意事項:
- 使用Redis/MySQL等客戶端理應使用線程池,參照官方Swoole框架。
- Swoole是在執(zhí)行PHP文件這一階段進行接管,因此header,setCooike,seesion_start不可用,header和setCooike可用$response變量實現(xiàn),Session可自行實現(xiàn)。
- 推薦查看官方框架:Swoole框架。
上述demo可以戳這里:demo
總結
Swoole的應用遠不如此,Swoole就像打開了PHP世界的新大門,我覺得每一位PHPer都應該學習和掌握Swoole的用法,能學到很多使用LAMP/LNMP模式時未涉及到的知識點。
? ??? Swoole? ??? ?? ??? ?????.? ?? ?????. ??? ??? PHP ??? ????? ?? ?? ??? ?????!

? AI ??

Undress AI Tool
??? ???? ??

Undresser.AI Undress
???? ?? ??? ??? ?? AI ?? ?

AI Clothes Remover
???? ?? ???? ??? AI ?????.

Clothoff.io
AI ? ???

Video Face Swap
??? ??? AI ?? ?? ??? ???? ?? ???? ??? ?? ????!

?? ??

??? ??

???++7.3.1
???? ?? ?? ?? ???

SublimeText3 ??? ??
??? ??, ???? ?? ????.

???? 13.0.1 ???
??? PHP ?? ?? ??

???? CS6
??? ? ?? ??

SublimeText3 Mac ??
? ??? ?? ?? ?????(SublimeText3)

Laravel?? Swoole ???? ???? ?? ?? ??? ??? ??? ? ????. ??? ??? ????. ?? ??: ?? ??? ??? ??? ? ????. ???: Linux epoll ??? ????? ???? ??? ????? ?????. ?? ??? ??: ? ?? ?? ???? ?????. ??? ??: Laravel ?????? ???? ???? ??? ?????.

Swoole? ???? ??? HTTP ??? ??? ??? ???? ?? Swoole? PHP ??? ???? ?? ???, ???? ?? ???? ?? ????????. ??? ???? ??? ???? HTTP ??, WebSocket ?? ?? ???? ? ??? ? ????. ? ????? Swoole? ???? ??? HTTP ??? ??? ??? ???? ??? ???? ???? ?? ??? ?????. ?? ?? ?? ??? Swoole ?? ????? ???? ???.

Swoole? Workerman? ?? ??? PHP ?? ????????. ??? ??, ??? ?? ? ????? ? ??? Swoole? ?? ?? ?? ??? ?? ???? ???? ?? ????? ?????. Workerman? ?? ???? ?? ??? ??? ???? ????? ? ??? ???? API? ?? ???? ? ??? ??? ???? ?????.

?? ??: ???: Swoole? ??? ???? ??? ???? ? ????. ?? ??: Swoole? ??? ???? ??? ????? ?? ?? ??? ? ????. ??? ??: Swoole? ???? ? ?? ???? ?????. ?? ???: Swoole? ???? ?? ?? ????? API? ?????.

Swoole ???? ?? ????? ?? ??? ?????. ??? ??? ???? PID? ?????. ???? ????? "kill -15 PID"? ??????. ???? ???? ? ??? ?? ??? ??? ???? ???? ?? ?????.

Swoole? ?? ??: ?? ?? ??? ?? ???? ???? ?? ?? ???? ???? ??? ??? ?? ??? ???? ?? ??? ?? ?????. ???? ?? ??? ?? ??? ?? ?? ????? ???? ?? ??? ???? ???? ? ???? ?? ? ??? ?? ???? ?? ??? ????. ???? ???? PHP? ????? ??? ???? ?? ?? ??? ?? ?? ???? ??? ?? ??? ? ????. ??? Swoole ??? ?????? ???? ???? ???? ??? ?? ?? ??? ??? ? ????. ? ??? ???

Swoole ????? ?? ???? ????? ???? ????? ??? ? ????.

Swoole? ??? IO, ?? ????, ?? ???, ??? ? ?? ??? ???? ??? PHP ??? ???? ????????. ?? Swoole?? ???? WebSocket ?? ??? ??? ??? ??? ???? ? ??? ? ??? ??? ??????? ???? ? ???? ?????. ? ????? Swoole? ???? WebSocket ??? ???? ??? ???? ?? ?? ??? ?????. 1. ?? ?? Swoole? ???? WebSocket ??? ???? ?? Swoole ??? ???? ??? ???? ???. ???
