Swoole 是一個面向 PHP 的高效能的事件驅動、非同步、基於協程的並發函式庫。
透過Docker運行Swoole程式
docker run --rm phpswoole/swoole "php --ri swoole"
有關如何使用它的詳細信息,請參閱:如何使用此圖像。
https://wiki.swoole.com/
$http = new SwooleHttpServer('127.0.0.1', 9501);$http->set(['hook_flags' => SWOOLE_HOOK_ALL]);$http->on('request', function ($request, $response) { $結果=[]; Co::join([go(function () use (&$result) {$result['google'] = file_get_contents("https://www.google.com/"); }),go(function () use (&$result) {$result['taobao'] = file_get_contents("https://www.taobao.com/"); }) ]);$response->end(json_encode($result)); });$http->start();
Corun(function() {Cogo(function() {while(1) {sleep(1);$fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);echo fread ($fp, 8192), PHP_EOL; } });Cogo(function() {$fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);while(1) {$conn = stream_sockAM_SERVER_LISTEN);while(1) {$conn = stream_socket_accept($. ;fwrite($conn, '當地時間是' .date('n/j/Y g:i a')); } });Cogo(function() {$redis = new Redis();$redis->connect('127.0.0.1', 6379);while(true) {$redis->subscribe(['test'], function ($instance, $channelName, $message) {echo '新的redis訊息:'.$channelName, "==>", $message, PHP_EOL; }); } });Cogo(function() {$redis = new Redis();$redis->connect('127.0.0.1', 6379);$count = 0;while(true) {sleep(2);$redis- >發布('測試','你好,世界,計數='.$count++); } }); });
Swoole 在底層掛鉤了 PHP 的阻塞 io 函數,並自動將其轉換為非阻塞函數,使得這些函數可以在協程中並發呼叫。
ext-curl(支援 symfony 和 guzzle)
ext-redis
ext-mysqli
ext-pdo_mysql
ext-pdo_pgsql
ext-pdo_sqlite
ext-pdo_oracle
ext-pdo_odbc
流函數(例如stream_socket_client/stream_socket_server),支援TCP/UDP/UDG/Unix/SSL/TLS/FileSystem API/Pipe
外部插座
外皂
睡眠/usleep/time_sleep_until
行程開啟
gethostbyname/shell_exec/exec
fread/fopen/fsockopen/fwrite/flock
IDE 幫助器和 API:https://github.com/swoole/ide-helper
推特:https://twitter.com/phpswoole
不和諧:https://discord.swoole.dev
中文社群:https://wiki.swoole.com/#/other/discussion
Project Awesome Swoole 維護著與 Swoole 相關的精彩內容的精選列表,包括
基於 Swoole 的框架和函式庫。
將 Swoole 與流行的 PHP 框架整合的軟體包,包括 Laravel、Symfony、Slim 和 Yii。
有關 Swoole 的書籍、影片和其他學習材料。
用於開發基於 Swoole 的應用程式的調試、分析和測試工具。
協程友好的包和庫。
其他 Swoole 相關項目和資源。
Swoole 中的網路層是基於事件的,並充分利用了底層的 epoll/kqueue 實現,使得服務數百萬個請求變得非常容易。
Swoole 4.x採用了全新的引擎內核,現在已經擁有了全職的開發團隊,因此我們正在進入PHP歷史上前所未有的時期,這為性能的快速進化提供了獨特的可能性。
Swoole 4.x 或更高版本支援高可用的內建協程,您可以使用完全同步的程式碼來實現非同步效能。 PHP程式碼無需任何額外關鍵字,底層自動協程調度。
開發者可以將協程理解為超輕量級線程,可以輕鬆地在單一進程中創建數千個協程。
並發10K請求從MySQL讀取資料僅需0.2s!
$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () {$mysql = new SwooleCoroutineMySQL;$mysql->connect(['主機' => '127.0.0.1','用戶' => 'root','密碼' => 'root','資料庫' => '測試']);$statement = $mysql->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$result = $statement->execute();assert(count($result) > 0); } }); } });回顯「使用」。 (microtime(true) - $s) 。 's';
您可以在單一事件循環上建立多個服務:TCP、HTTP、Websocket 和 HTTP2,並輕鬆處理數千個請求。
函數 tcp_pack(string $data): string{return pack('N', strlen($data)) 。 $數據; }function tcp_unpack(string $data): string{return substr($data, 4, unpack('N', substr($data, 0, 4))[1]); }$tcp_options = ['open_length_check' => true,'package_length_type' => 'N','package_length_offset' => 0,'package_body_offset' => 4];
$server = new SwooleWebSocketServer('127.0.0.1', 9501, SWOOLE_BASE);$server->set(['open_http2_protocol' => true]);// http && http2$server->on('request', function ( SwooleHttpRequest $request, SwooleHttpResponse $response) {$response->end('Hello ' . $request->rawcontent()); });// websocket$server->on('message', function (SwooleWebSocketServer $server, SwooleWebSocketFrame $frame) {$server->push($frame->fd, 'Hello ' . $frame->data); });// tcp$tcp_server = $server->listen('127.0.0.1', 9502, SWOOLE_TCP);$tcp_server->set($tcp_options);$tcp_server->on('接收', function (SwooleServer $ server, int $fd, int $reactor_id, string $data) {$server->send($fd, tcp_pack('Hello ' . tcp_unpack($data))); });$伺服器->開始();
無論您進行 DNS 查詢還是發送請求或接收回應,所有這些都是由協程自動調度的。
go(function () {// http$http_client = new SwooleCoroutineHttpClient('127.0.0.1', 9501);assert($http_client->post('/', 'Swoole Http'));var_dump(client->post('/', 'Swoole Http'));var_dump(body$http_client->body$ );// websocket$http_client->upgrade('/');$http_client->push('Swoole Websocket');var_dump($http_client->recv()->data); });go(function () {// http2$http2_client = new SwooleCoroutineHttp2Client('localhost', 9501);$http2_client->connect();$http2_request = new SwooleHttp2Request;http2_request-$http2_request = new SwooleHttp2Request;http2_request-'method; http2_request->data = 'Swoole Http2';$http2_client->send($http2_request);$http2_response = $http2_client->recv();var_dump($http2_response->data); });go(function () use ($tcp_options) {// tcp$tcp_client = new SwooleCoroutineClient(SWOOLE_TCP);$tcp_client->set($tcp_options);$tcp_client->connect('127.0.051. ;$tcp_client->send(tcp_pack('Swoole TCP'));var_dump(tcp_unpack($tcp_client->recv())); });
Channel是協程之間交換資料的唯一途徑,Coroutine+Channel的開發組合就是著名的CSP程式設計模型。
在 Swoole 開發中,Channel 通常用於實現連線池或調度協程並發。
在下面的範例中,我們有一千個並發請求到 redis。通常情況下,這已經超出了Redis最大連接數設置,會拋出連接異常,但是基於Channel的連接池可以完美調度請求。我們不必擔心連接過載。
RedisPool 類 {/**@var SwooleCoroutineChannel */protected $pool;/** * RedisPool 建構子。 * @param int $size 最大連線數 */public function __construct(int $size = 100) {$this->pool = new SwooleCoroutineChannel($size);for ($i = 0; $i < $size; $i++) {$redis = new SwooleCoroutineRedis();$res = $redis->connect('127.0 .0.1', 6379);if ($res == false) { throw new RuntimeException("連接redis伺服器失敗。"); } else {$this->put($redis); } } }公用函數 get(): SwooleCoroutineRedis{return $this->pool->pop(); }公用函數 put(SwooleCoroutineRedis $redis) {$this->池->push($redis); }公用函數 close(): void{$this->pool->close();$this->pool = null; } }go(function () {$pool = new RedisPool();//最大並發數大於最大連線數//不過沒關係,channel會幫你調度for ($c = 0; $c < 1000; $ c++) { go(function () use ($pool, $c) {for ($n = 0; $n < 100; $n++) {$redis = $pool->get();assert($redis-> set(" awesome-{$c}-{$n}", 'swoole'));assert($redis->get("awesome-{$c}-{$n}") === 'swoole' );assert ($redis->delete("真棒-{$c}-{$n}"));$pool->put($redis); } }); } });
一些 Swoole 的客戶端實作了 defer 模式來實現並發,但你仍然可以透過協程和通道的組合來靈活地實現它。
go(function () {// 使用者:我需要你帶一些資訊 // Channel:好的!我來負責調度。$channel = new SwooleCoroutineChannel;go(function () use ($channel) { // 協程A:好的! });go(function () use ($channel) {// 協程B:好的!我會告訴你你的程式碼是什麼樣的$mirror = Co::readFile(__FILE__);$channel->push (['B ', $mirror]); });go(function () use ($channel) {// 協程 C:好的!我將向您展示日期$channel->push(['C', date(DATE_W3C)]); });for ($i = 3; $i--;) {list($id, $data) = $channel->pop();echo "來自 {$id}:n {$data}n"; }// 使用者:太棒了,我第一時間拿到了所有資訊!
$id = SwooleTimer::tick(100, function () {echo " 做某事...n"; });SwooleTimer::after(500, function () use ($id) {SwooleTimer::clear($id);echo "⏰ Donen"; });SwooleTimer::after(1000, function () use ($id) {if (!SwooleTimer::exists($id)) {echo " 好!n"; } });
去(函數(){$ i = 0; while(true){ Co::sleep(0.1);echo "做某事...n";if (++$i === 5) {echo "Donen";break; } }echo「好!n」; });
從 Swoole v4.1.0 開始,我們新增了使用一行程式碼將同步 PHP 網路庫轉換為協程庫的功能。
只需呼叫腳本頂部的 SwooleRuntime::enableCoroutine() 方法即可。在下面的範例中,我們連接到 php-redis 並在 0.1 秒內並發讀取 10k 請求:
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () { ($redis = new Redis)->connect('127.0.0.1', 6379);for ($n = 100; $n--;) {assert($redis->get('awesome') === ' swoole'); } }); } });回顯「使用」。 (microtime(true) - $s) 。 's';
透過呼叫該方法,Swoole內核替換了ZendVM流函數指標。如果您使用基於 php_stream 的擴展,所有套接字操作都可以在運行時動態轉換為由協程調度的非同步 IO!
睡眠10K次,讀取、寫入、檢查和刪除檔案10K次,使用PDO和MySQLi與資料庫通訊10K次,建立TCP伺服器和多個客戶端相互通訊10K次,建立UDP伺服器和多個客戶端相互通訊10K次相互通信10K 次...一切都在一個過程中運作良好!
看看 Swoole 帶來了什麼,想像一下...
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {// 我只想睡覺...for ($c = 100; $c--;) {go(function () { for ($n = 100; $n--;) {usleep(1000); } }); }// 10K檔案讀寫for ($c = 100; $c--;) {go(function () use ($c) {$tmp_filename = "/tmp/test-{$c}.php";for ($n = 100; $n--;) {$self = file_get_contents(__FILE__);file_put_contents($tmp_filename, $self);assert(file_get_contents($tmp_filename) === $self); }取消連結($tmp_檔名); }); }// 10K pdo 和 mysqli readfor ($c = 50; $c--;) {go(function () {$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8' , 'root', 'root');$statement = $pdo->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$statement->execute() ;assert(count($statement->fetchAll()) > 0); } }); }for ($c = 50; $c--;) {go(function () {$mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test');$statement = $ mysqli->prepare('SELECT `id` FROM `user`');for ($n = 100; $n--;) {$statement->bind_result($id);$statement->execute();$語句->fetch();assert($id > 0); } }); }// php_stream tcp 伺服器和客戶端在單一程序中處理 12.8K 請求 function tcp_pack(string $data): string{return pack('n', strlen($data)) . $數據; }function tcp_length(string $head): int{return unpack('n', $head)[1]; }go(function () {$ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]);$socket = stream_socket_server('tcp://0.0.0.0: 9502',$errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx);if (!$socket) {echo "$errstr ($errno)n"; } else {$i = 0;while ($conn = stream_socket_accept($socket, 1)) {stream_set_timeout($conn, 5);for ($n = 100; $n--;) {$data = fread($ conn, tcp_length(fread($conn, 2)));assert($data === "Hello Swoole Server #{$n}!");fwrite($conn, <span class=