Swoole is an event-driven, asynchronous, coroutine-based concurrency library with high performance for PHP.
Run Swoole program by Docker
docker run --rm phpswoole/swoole "php --ri swoole"
For details on how to use it, see: How to Use This Image.
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) {$result = []; 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_socket_accept($fp);fwrite($conn, 'The local time is ' . 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 'New redis message: '.$channelName, "==>", $message, PHP_EOL; }); } });Cogo(function() {$redis = new Redis();$redis->connect('127.0.0.1', 6379);$count = 0;while(true) {sleep(2);$redis->publish('test','hello, world, count='.$count++); } }); });
Swoole hooks the blocking io function of PHP at the bottom layer and automatically converts it to a non-blocking function, so that these functions can be called concurrently in coroutines.
ext-curl (Support symfony and guzzle)
ext-redis
ext-mysqli
ext-pdo_mysql
ext-pdo_pgsql
ext-pdo_sqlite
ext-pdo_oracle
ext-pdo_odbc
stream functions (e.g. stream_socket_client/stream_socket_server), Supports TCP/UDP/UDG/Unix/SSL/TLS/FileSystem API/Pipe
ext-sockets
ext-soap
sleep/usleep/time_sleep_until
proc_open
gethostbyname/shell_exec/exec
fread/fopen/fsockopen/fwrite/flock
IDE Helper & API: https://github.com/swoole/ide-helper
Twitter: https://twitter.com/phpswoole
Discord: https://discord.swoole.dev
中文社区: https://wiki.swoole.com/#/other/discussion
Project Awesome Swoole maintains a curated list of awesome things related to Swoole, including
Swoole-based frameworks and libraries.
Packages to integrate Swoole with popular PHP frameworks, including Laravel, Symfony, Slim, and Yii.
Books, videos, and other learning materials about Swoole.
Debugging, profiling, and testing tools for developing Swoole-based applications.
Coroutine-friendly packages and libraries.
Other Swoole related projects and resources.
The network layer in Swoole is event-based and takes full advantage of the underlying epoll/kqueue implementation, making it really easy to serve millions of requests.
Swoole 4.x uses a brand new engine kernel and now it has a full-time developer team, so we are entering an unprecedented period in PHP history which offers a unique possibility for rapid evolution in performance.
Swoole 4.x or later supports the built-in coroutine with high availability, and you can use fully synchronized code to implement asynchronous performance. PHP code without any additional keywords, the underlying automatic coroutine-scheduling.
Developers can understand coroutines as ultra-lightweight threads, and you can easily create thousands of coroutines in a single process.
Concurrency 10K requests to read data from MySQL takes only 0.2s!
$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () {$mysql = new SwooleCoroutineMySQL;$mysql->connect(['host' => '127.0.0.1','user' => 'root','password' => 'root','database' => 'test']);$statement = $mysql->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$result = $statement->execute();assert(count($result) > 0); } }); } });echo 'use ' . (microtime(true) - $s) . ' s';
You can create multiple services on the single event loop: TCP, HTTP, Websocket and HTTP2, and easily handle thousands of requests.
function tcp_pack(string $data): string{return pack('N', strlen($data)) . $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('receive', function (SwooleServer $server, int $fd, int $reactor_id, string $data) {$server->send($fd, tcp_pack('Hello ' . tcp_unpack($data))); });$server->start();
Whether you DNS query or send requests or receive responses, all of these are scheduled by coroutine automatically.
go(function () {// http$http_client = new SwooleCoroutineHttpClient('127.0.0.1', 9501);assert($http_client->post('/', 'Swoole Http'));var_dump($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->method = 'POST';$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.0.1', 9502);$tcp_client->send(tcp_pack('Swoole Tcp'));var_dump(tcp_unpack($tcp_client->recv())); });
Channel is the only way for exchanging data between coroutines, the development combination of the Coroutine + Channel is the famous CSP programming model.
In Swoole development, Channel is usually used for implementing connection pool or scheduling coroutine concurrent.
In the following example, we have a thousand concurrently requests to redis. Normally, this has exceeded the maximum number of Redis connections setting and will throw a connection exception, but the connection pool based on Channel can perfectly schedule requests. We don't have to worry about connection overload.
class RedisPool {/**@var SwooleCoroutineChannel */protected $pool;/** * RedisPool constructor. * @param int $size max connections */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("failed to connect redis server."); } else {$this->put($redis); } } }public function get(): SwooleCoroutineRedis{return $this->pool->pop(); }public function put(SwooleCoroutineRedis $redis) {$this->pool->push($redis); }public function close(): void{$this->pool->close();$this->pool = null; } }go(function () {$pool = new RedisPool();// max concurrency num is more than max connections// but it's no problem, channel will help you with schedulingfor ($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("awesome-{$c}-{$n}"));$pool->put($redis); } }); } });
Some Swoole's clients implement the defer mode for concurrency, but you can still implement it flexible with a combination of coroutines and channels.
go(function () {// User: I need you to bring me some information back.// Channel: OK! I will be responsible for scheduling.$channel = new SwooleCoroutineChannel;go(function () use ($channel) {// Coroutine A: Ok! I will show you the github addr info$addr_info = Co::getaddrinfo('github.com');$channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]); });go(function () use ($channel) {// Coroutine B: Ok! I will show you what your code look like$mirror = Co::readFile(__FILE__);$channel->push(['B', $mirror]); });go(function () use ($channel) {// Coroutine C: Ok! I will show you the date$channel->push(['C', date(DATE_W3C)]); });for ($i = 3; $i--;) {list($id, $data) = $channel->pop();echo "From {$id}:n {$data}n"; }// User: Amazing, I got every information at earliest time!});
$id = SwooleTimer::tick(100, function () {echo " Do something...n"; });SwooleTimer::after(500, function () use ($id) {SwooleTimer::clear($id);echo "⏰ Donen"; });SwooleTimer::after(1000, function () use ($id) {if (!SwooleTimer::exists($id)) {echo " All right!n"; } });
go(function () {$i = 0;while (true) { Co::sleep(0.1);echo " Do something...n";if (++$i === 5) {echo " Donen";break; } }echo " All right!n"; });
As of Swoole v4.1.0, we added the ability to transform synchronous PHP network libraries into co-routine libraries using a single line of code.
Simply call the SwooleRuntime::enableCoroutine() method at the top of your script. In the sample below we connect to php-redis and concurrently read 10k requests in 0.1s:
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'); } }); } });echo 'use ' . (microtime(true) - $s) . ' s';
By calling this method, the Swoole kernel replaces ZendVM stream function pointers. If you use php_stream based extensions, all socket operations can be dynamically converted to be asynchronous IO scheduled by coroutine at runtime!
Sleep 10K times, read, write, check and delete files 10K times, use PDO and MySQLi to communicate with the database 10K times, create a TCP server and multiple clients to communicate with each other 10K times, create a UDP server and multiple clients to communicate with each other 10K times... Everything works well in one process!
Just see what the Swoole brings, just imagine...
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {// i just want to sleep...for ($c = 100; $c--;) {go(function () {for ($n = 100; $n--;) {usleep(1000); } }); }// 10K file read and writefor ($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); }unlink($tmp_filename); }); }// 10K pdo and 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();$statement->fetch();assert($id > 0); } }); }// php_stream tcp server & client with 12.8K requests in single processfunction tcp_pack(string $data): string{return pack('n', strlen($data)) . $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=