Swoole เป็นไลบรารีการทำงานพร้อมกันแบบอะซิงโครนัสที่ขับเคลื่อนด้วยเหตุการณ์และอิงตาม Coroutine ซึ่งมีประสิทธิภาพสูงสำหรับ PHP
รันโปรแกรม Swoole โดย Docker
นักเทียบท่าวิ่ง --rm phpswoole/swoole "php --ri swoole"
สำหรับรายละเอียดเกี่ยวกับวิธีใช้งาน โปรดดู: วิธีใช้ภาพนี้
https://wiki.swoole.com/
$http = ใหม่ 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->เริ่มต้น();
Corun(function() {Cogo(function() {ขณะ(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); ในขณะที่(1) {$conn = stream_socket_accept($fp) ;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'], ฟังก์ชัน ($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- >เผยแพร่('test','hello, world, count='.$count++); - - -
Swoole เชื่อมต่อฟังก์ชัน blocking io ของ PHP ที่ชั้นล่างสุดและแปลงเป็นฟังก์ชันที่ไม่ปิดกั้นโดยอัตโนมัติ เพื่อให้สามารถเรียกใช้ฟังก์ชันเหล่านี้พร้อมกันใน coroutines
ext-curl (รองรับ symfony และ guzzle)
ต่อ Redis
ต่อ mysqli
ต่อ-pdo_mysql
ต่อ-pdo_pgsql
ต่อ-pdo_sqlite
ต่อ-pdo_oracle
ต่อ-pdo_odbc
ฟังก์ชั่นสตรีม (เช่น stream_socket_client/stream_socket_server) รองรับ TCP/UDP/UDG/Unix/SSL/TLS/FileSystem API/Pipe
ปลั๊กต่อ
ต่อสบู่
นอนหลับ/usleep/time_sleep_until
proc_open
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
แพ็คเกจและไลบรารีที่เป็นมิตรกับ Coroutine
โครงการและทรัพยากรอื่นๆ ที่เกี่ยวข้องกับ Swoole
เลเยอร์เครือข่ายใน Swoole เป็นไปตามเหตุการณ์และใช้ประโยชน์จากการใช้งาน epoll/kqueue อย่างเต็มที่ ทำให้ง่ายต่อการให้บริการตามคำขอนับล้านรายการ
Swoole 4.x ใช้เคอร์เนลเอ็นจิ้นใหม่ล่าสุด และตอนนี้ก็มีทีมนักพัฒนาเต็มเวลาแล้ว ดังนั้นเราจึงเข้าสู่ยุคที่ไม่เคยมีมาก่อนในประวัติศาสตร์ PHP ซึ่งเสนอความเป็นไปได้ที่ไม่เหมือนใครสำหรับการพัฒนาประสิทธิภาพอย่างรวดเร็ว
Swoole 4.x หรือใหม่กว่ารองรับ Coroutine ในตัวที่มีความพร้อมใช้งานสูงและคุณสามารถใช้โค้ดที่ซิงโครไนซ์อย่างสมบูรณ์เพื่อปรับใช้ประสิทธิภาพแบบอะซิงโครนัส โค้ด PHP ที่ไม่มีคีย์เวิร์ดเพิ่มเติม ซึ่งเป็นการตั้งเวลาโครูทีนอัตโนมัติ
นักพัฒนาสามารถเข้าใจ coroutines ว่าเป็นเธรดที่มีน้ำหนักเบาเป็นพิเศษ และคุณสามารถสร้าง coroutines หลายพันรายการได้อย่างง่ายดายในกระบวนการเดียว
คำขอ 10K เพื่ออ่านข้อมูลจาก MySQL ใช้เวลาเพียง 0.2 วินาที!
$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 'ใช้ ' (ไมโครไทม์(จริง) - $s) ' s';
คุณสามารถสร้างบริการได้หลายบริการในเหตุการณ์เดียว: TCP, HTTP, Websocket และ HTTP2 และจัดการคำขอนับพันรายการได้อย่างง่ายดาย
ฟังก์ชั่น tcp_pack(string $data): string{return pack('N', strlen($data)) $ข้อมูล; }ฟังก์ชั่น 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 = ใหม่ SwooleWebSocketServer('127.0.0.1', 9501, SWOOLE_BASE);$server->set(['open_http2_protocol' => true]);// http && http2$server->on('request', function ( SwooleHttpRequest $ คำขอ, SwooleHttpResponse $ ตอบกลับ) {$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 $ เซิร์ฟเวอร์, int $fd, int $reactor_id, สตริง $data) {$server->send($fd, tcp_pack('สวัสดี ' . tcp_unpack($data))); });$เซิร์ฟเวอร์->เริ่มต้น();
ไม่ว่าคุณจะสืบค้น DNS หรือส่งคำขอหรือรับการตอบกลับ ทั้งหมดนี้จะถูกกำหนดเวลาโดย Coroutine โดยอัตโนมัติ
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 = 'สวูล' 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 เป็นวิธีเดียวในการแลกเปลี่ยนข้อมูลระหว่าง Coroutines การผสมผสานการพัฒนาของ Coroutine + Channel คือโมเดลการเขียนโปรแกรม CSP ที่มีชื่อเสียง
ในการพัฒนา Swoole นั้น Channel มักจะใช้สำหรับการนำพูลการเชื่อมต่อไปใช้หรือการกำหนดเวลา Coroutine พร้อมกัน
ในตัวอย่างต่อไปนี้ เรามีคำขอนับพันคำขอที่จะทำซ้ำพร้อมกัน โดยปกติ การตั้งค่าการเชื่อมต่อ Redis นี้เกินจำนวนสูงสุด และจะส่งข้อยกเว้นการเชื่อมต่อ แต่พูลการเชื่อมต่อตามช่องสัญญาณสามารถกำหนดเวลาคำขอได้อย่างสมบูรณ์แบบ เราไม่ต้องกังวลเกี่ยวกับการเชื่อมต่อเกินพิกัด
คลาส RedisPool {/**@var SwoolCoroutineChannel */ป้องกัน $pool;/** * ตัวสร้าง RedisPool * @param int $size การเชื่อมต่อสูงสุด */ ฟังก์ชั่นสาธารณะ __construct(int $size = 100) {$this->pool = ใหม่ SwooleCoroutineChannel($size);for ($i = 0; $i < $size; $i++) {$redis = ใหม่ SwooleCoroutineRedis();$res = $redis->connect('127.0 .0.1', 6379);if ($res == false) {throw new RuntimeException("ไม่สามารถเชื่อมต่อเซิร์ฟเวอร์ redis"); } อื่น ๆ {$this->put($redis); - - }ฟังก์ชันสาธารณะ get(): SwooleCoroutineRedis{return $this->pool->pop(); } ฟังก์ชั่นสาธารณะ (SwooleCoroutineRedis $redis) {$this->pool->push($redis); }ฟังก์ชันสาธารณะ close(): void{$this->pool->close();$this->pool = null; - }go(function () {$pool = new RedisPool();// จำนวนการทำงานพร้อมกันสูงสุดมากกว่าการเชื่อมต่อสูงสุด// แต่ก็ไม่มีปัญหา channel จะช่วยคุณในการตั้งเวลา ($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); - - - -
ไคลเอนต์ของ Swoole บางรายใช้โหมด defer สำหรับการใช้งานพร้อมกัน แต่คุณยังสามารถนำไปใช้ได้อย่างยืดหยุ่นด้วยการผสมผสานระหว่าง coroutines และช่องทาง
go(function () {// User: ฉันต้องการให้คุณนำข้อมูลกลับมาให้ฉัน// Channel: ตกลง! ฉันจะเป็นผู้รับผิดชอบในการตั้งเวลา.$channel = new SwoolCoroutineChannel;go(function () use ($channel) { // Coroutine A: โอเค ฉันจะแสดง 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: โอเค ฉันจะแสดงให้คุณเห็นว่าโค้ดของคุณหน้าตาเป็นอย่างไร$mirror = Co::readFile(__FILE__);$channel->push(['B ', $กระจกเงา]); });go(function () use ($channel) {// Coroutine C: โอเค ฉันจะแสดง date$channel->push(['C', date(DATE_W3C)]); });for ($i = 3; $i--;) {list($id, $data) = $channel->pop();echo "จาก {$id}:n {$data}n"; }// ผู้ใช้: น่าทึ่งมาก ฉันได้รับข้อมูลทุกอย่างตั้งแต่แรก!});
$id = SwoleTimer::tick(100, function () {echo " Do some...n"; });SwooleTimer::after(500, function () ใช้ ($id) {SwooleTimer::clear($id);echo "⏰ Donen"; });SwooleTimer::after(1000, function () use ($id) {if (!SwooleTimer::exists($id)) {echo " เอาล่ะ!n"; - -
ไป (ฟังก์ชั่น () {$i = 0; ในขณะที่ (จริง) { Co::sleep(0.1);echo " Do Something...n";if (++$i === 5) {echo " Donen";break; - } echo " เอาล่ะ!n"; -
ตั้งแต่ Swoole เวอร์ชัน 4.1.0 เราได้เพิ่มความสามารถในการแปลงไลบรารีเครือข่าย PHP แบบซิงโครนัสให้เป็นไลบรารีรูทีนร่วมโดยใช้โค้ดบรรทัดเดียว
เพียงเรียกเมธอด SwooleRuntime::enableCoroutine() ที่ด้านบนของสคริปต์ ในตัวอย่างด้านล่าง เราเชื่อมต่อกับ php-redis และอ่านคำขอ 10,000 รายการพร้อมกันใน 0.1 วินาที:
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {สำหรับ ($c = 100; $c--;) {go(ฟังก์ชั่น () { ($redis = Redis ใหม่)->เชื่อมต่อ('127.0.0.1', 6379);สำหรับ ($n = 100; $n--;) {assert($redis->get('awesome') === ' สวูล'); - - - });echo 'ใช้ ' (ไมโครไทม์(จริง) - $s) ' s';
ด้วยการเรียกเมธอดนี้ เคอร์เนล Swoole จะแทนที่ตัวชี้ฟังก์ชันสตรีม ZendVM หากคุณใช้ส่วนขยายที่ใช้ php_stream การดำเนินการของซ็อกเก็ตทั้งหมดสามารถแปลงแบบไดนามิกให้เป็น IO แบบอะซิงโครนัสที่กำหนดเวลาโดย coroutine ณ รันไทม์!
นอนหลับ 10,000 ครั้ง อ่าน เขียน ตรวจสอบและลบไฟล์ 10,000 ครั้ง ใช้ PDO และ MySQLi เพื่อสื่อสารกับฐานข้อมูล 10,000 ครั้ง สร้างเซิร์ฟเวอร์ TCP และไคลเอนต์หลายตัวเพื่อสื่อสารกัน 10,000 ครั้ง สร้างเซิร์ฟเวอร์ UDP และไคลเอนต์หลายตัวเพื่อ สื่อสารกัน 10,000 ครั้ง... ทุกอย่างทำงานได้ดีในกระบวนการเดียว!
แค่ดูว่า Swoole นำมาซึ่งอะไร ลองจินตนาการ...
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {// ฉันแค่อยากนอน...for ($c = 100; $c--;) {go(function () {สำหรับ ($n = 100; $n--;) {usleep(1,000); - - }// อ่านและเขียนไฟล์ขนาด 10K ($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_filename); - }// 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); - - }สำหรับ ($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 พร้อมคำขอ 12.8K ในฟังก์ชันกระบวนการเดียว tcp_pack(string $data): string{return pack('n', strlen($data)) $ข้อมูล; }ฟังก์ชั่น 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"; } อื่น ๆ {$i = 0; ในขณะที่ ($conn = stream_socket_accept($socket, 1)) {stream_set_timeout($conn, 5);สำหรับ ($n = 100; $n--;) {$data = fread($ conn, tcp_length(fread($conn, 2)));assert($data === "สวัสดีเซิร์ฟเวอร์ Swoole #{$n}!");fwrite($conn, <span class=