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(함수 () 사용 (&$result) {$result['taobao'] = file_get_contents("https://www.taobao.com/"); }) ]);$response->end(json_encode($result)); });$http->시작();
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, '현지 시간은 ' 입니다. 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- >publish('test','hello, world, count='.$count++); } }); });
Swoole은 PHP의 블로킹 io 함수를 맨 아래 레이어에 연결하고 자동으로 비차단 함수로 변환하므로 이러한 함수를 코루틴에서 동시에 호출할 수 있습니다.
ext-curl (symfony 및 guzzle 지원)
외부 레디스
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
proc_open
gethostbyname/shell_exec/exec
fread/fopen/fsockopen/fwrite/무리
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 기반 프레임워크 및 라이브러리.
Laravel, Symfony, Slim 및 Yii를 포함하여 널리 사용되는 PHP 프레임워크와 Swoole을 통합하는 패키지입니다.
Swoole에 관한 책, 비디오 및 기타 학습 자료입니다.
Swoole 기반 애플리케이션 개발을 위한 디버깅, 프로파일링 및 테스트 도구입니다.
코루틴 친화적인 패키지 및 라이브러리.
기타 Swoole 관련 프로젝트 및 리소스.
Swoole의 네트워크 계층은 이벤트 기반이며 기본 epoll/kqueue 구현을 최대한 활용하므로 수백만 건의 요청을 매우 쉽게 처리할 수 있습니다.
Swoole 4.x는 완전히 새로운 엔진 커널을 사용하며 현재는 정규 개발자 팀을 보유하고 있습니다. 따라서 우리는 성능의 급속한 발전을 위한 고유한 가능성을 제공하는 PHP 역사상 전례 없는 시기로 진입하고 있습니다.
Swoole 4.x 이상에서는 고가용성 내장 코루틴을 지원하며 완전히 동기화된 코드를 사용하여 비동기 성능을 구현할 수 있습니다. 추가 키워드가 없는 PHP 코드, 기본 자동 코루틴 스케줄링.
개발자는 코루틴을 초경량 스레드로 이해할 수 있으며 단일 프로세스에서 수천 개의 코루틴을 쉽게 만들 수 있습니다.
MySQL에서 데이터를 읽기 위한 동시 10K 요청은 0.2초 밖에 걸리지 않습니다!
$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () {$mysql = new SwooleCoroutineMySQL;$mysql->connect(['host' => '127.0.0.1','사용자' => '루트','비밀번호' => '루트','데이터베이스' => '테스트']);$statement = $mysql->prepare('SELECT * FROM `user`');for ($n = 100; $n--;) {$result = $statement->execute();assert(count($result) > 0 ); } }); } });echo '사용' . (마이크로타임(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('요청', function ( SwooleHttpRequest $request, SwooleHttpResponse $response) {$response->end('안녕하세요' . $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('수신', 함수(SwooleServer $ 서버, int $fd, int $reactor_id, 문자열 $data) {$server->send($fd, tcp_pack('안녕하세요' . tcp_unpack($data))); });$서버->시작();
DNS 쿼리, 요청 전송, 응답 수신 등 모든 작업은 코루틴에 의해 자동으로 예약됩니다.
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(함수 () {// 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(함수 () 사용 ($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())); });
채널은 코루틴 간에 데이터를 교환하는 유일한 방법이며, 코루틴 + 채널의 개발 조합은 유명한 CSP 프로그래밍 모델입니다.
Swoole 개발에서 채널은 일반적으로 연결 풀을 구현하거나 코루틴 동시 예약에 사용됩니다.
다음 예에는 redis에 대한 동시 요청이 수천 개 있습니다. 일반적으로 이는 최대 Redis 연결 수 설정을 초과하여 연결 예외가 발생하지만 채널 기반 연결 풀은 요청을 완벽하게 예약할 수 있습니다. 연결 과부하에 대해 걱정할 필요가 없습니다.
클래스 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->풀->푸시($redis); }공용 함수 close(): void{$this->pool->close();$this->pool = null; } }go(function () {$pool = new RedisPool();// 최대 동시성 수는 최대 연결보다 많습니다// 하지만 문제 없습니다. 채널이 스케줄링에 도움을 줄 것입니다($c = 0; $c < 1000; $ c++) {go(함수 () 사용 ($pool, $c) {for ($n = 0; $n < 100; $n++) {$redis = $pool->get();assert($redis->set("굉장해요-{$c}-{$n}", 'swoole'));assert($redis->get("굉장해요-{$c }-{$n}") === 'swoole');assert($redis->delete("굉장해요-{$c}-{$n}"));$pool->put($redis); } }); } });
일부 Swoole 클라이언트는 동시성을 위해 지연 모드를 구현하지만 코루틴과 채널을 조합하여 유연하게 구현할 수도 있습니다.
go(function () {// 사용자: 몇 가지 정보를 다시 가져와야 합니다.// 채널: 알겠습니다! 일정 관리는 제가 담당하겠습니다.$channel = new SwooleCoroutineChannel;go(function () use ($channel) { // 코루틴 A: 좋아요! github 주소를 보여드리겠습니다. info$addr_info = Co::getaddrinfo('github.com');$channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]); });go(function () use ($channel) {// 코루틴 B: 좋습니다! 코드가 어떻게 생겼는지 보여드리겠습니다.$mirror = Co::readFile(__FILE__);$channel->push(['B ', $mirror]); });go(function () use ($channel) {// 코루틴 C: 좋습니다! date$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"; } });
go(함수 () {$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초 안에 10,000개의 요청을 동시에 읽습니다.
SwooleRuntime::enableCoroutine();$s = microtime(true);Corun(function() {for ($c = 100; $c--;) {go(function () { ($redis = 새로운 Redis)->connect('127.0.0.1', 6379);for ($n = 100; $n--;) {assert($redis->get('awesome') === ' 스울'); } }); } });echo '사용' . (마이크로타임(true) - $s) . 's';
이 메서드를 호출하면 Swoole 커널이 ZendVM 스트림 함수 포인터를 대체합니다. php_stream 기반 확장을 사용하는 경우 모든 소켓 작업은 런타임 시 코루틴에 의해 예약된 비동기 IO로 동적으로 변환될 수 있습니다!
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 () {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_filename); }); }// 10K pdo 및 mysqli readfor ($c = 50; $c--;) {go(function () {$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8' , '루트', '루트');$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); } }); }// 단일 프로세스 함수에서 12.8K 요청을 포함하는 php_stream tcp 서버 및 클라이언트 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 | $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 === "안녕 Swoole 서버 #{$n}!");fwrite($conn, <span 클래스=