php 프로그래머는 모두 php로 작성된 프로그램이 모두 동기식이라는 것을 알고 있습니다. php에서 비동기식 프로그램을 작성하는 방법은 무엇입니까? 대답은 Swoole입니다. 여기서는 웹 콘텐츠 크롤링을 예로 들어 Swoole을 사용하여 비동기 프로그램을 작성하는 방법을 보여줍니다.
PHP 동기화 프로그램
비동기 프로그램을 작성하기 전에 걱정하지 마세요. 먼저 PHP를 사용하여 동기화 프로그램을 구현해 보세요.
<?php /** * Class Crawler * Path: /Sync/Crawler.php */ class Crawler { private $url; private $toVisit = []; public function __construct($url) { $this->url = $url; } public function visitOneDegree() { $this->loadPageUrls(); $this->visitAll(); } private function loadPageUrls() { $content = $this->visit($this->url); $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url) { return @file_get_contents($url); } }
<?php /** * crawler.php */ require_once 'Sync/Crawler.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(); $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; /* output: time used: 6.2610177993774 */
Swoole의 비동기 크롤러 구현에 대한 예비 연구
먼저 공식 비동기 크롤링 페이지를 참고하세요.
사용예
Swoole\Async::dnsLookup("www.baidu.com", function ($domainName, $ip) { $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get('/index.html', function ($cli) { echo "Length: " . strlen($cli->body) . "\n"; echo $cli->body; }); });
동기식 file_get_contents 코드를 조금만 수정하면 비동기식 구현이 가능한 것 같습니다.
그래서 우리는 다음 코드를 얻었습니다:
<?php /** * Class Crawler * Path: /Async/CrawlerV1.php */ class Crawler { private $url; private $toVisit = []; private $loaded = false; public function __construct($url) { $this->url = $url; } public function visitOneDegree() { $this->visit($this->url, true); $retryCount = 3; do { sleep(1); $retryCount--; } while ($retryCount > 0 && $this->loaded == false); $this->visitAll(); } private function loadPage($content) { $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url, $root = false) { $urlInfo = parse_url($url); Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo, $root) { $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get($urlInfo['path'], function ($cli) use ($root) { if ($root) { $this->loadPage($cli->body); $this->loaded = true; } }); }); } }
<?php /** * crawler.php */ require_once 'Async/CrawlerV1.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(); $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; /* output: time used: 3.011773109436 */
실행하는 데 3초가 걸렸습니다. 내 구현에 주의하세요. 홈페이지 크롤링 요청을 시작한 후 매초 결과를 폴링하고 세 번 폴링한 후에 종료됩니다. 여기서 3초는 결과 없이 3번의 폴링으로 인한 이탈인 것 같습니다.
제가 너무 성급해서 준비시간을 충분히 주지 못한 것 같아요. 좋습니다. 설문 조사 수를 10으로 변경하고 결과를 살펴보겠습니다.
time used: 10.034232854843
지금 내 기분이 어떤지 아시죠?
스울의 성능 문제인가요? 10초가 지나도 결과가 나오지 않는 이유는 자세가 잘못되었기 때문일까요? 노인 마르크스는 "진리를 검증하는 유일한 기준은 실천이다"라고 말했습니다. 원인을 찾으려면 디버깅을 해봐야 할 것 같습니다.
그래서
$this->visitAll();
및
$this->loadPage($cli->body);
에 중단점을 추가했습니다. 마지막으로 항상 VisitAll()이 먼저 실행되고 그 다음에 loadPage()가 실행되는 것을 발견했습니다.
잠깐 생각해보면 이유를 알 것 같아요. 이유는 무엇입니까?
제가 예상했던 비동기 동적 모델은 이렇습니다.
그러나 실제 장면은 그렇지 않습니다. 디버깅을 통해 실제 모델은 다음과 같아야 한다는 것을 대략적으로 이해합니다.
즉, 재시도 횟수를 아무리 늘려도 데이터는 절대 준비되지 않고 데이터만 준비됩니다. 현재 기능이 준비된 후 실행이 시작됩니다. 여기서 비동기식은 연결을 준비하는 시간만 단축합니다.
그럼 질문은 데이터를 준비한 후 프로그램이 내가 기대하는 기능을 수행하도록 하려면 어떻게 해야 하느냐는 것입니다.
먼저 Swoole의 비동기 작업 실행 공식 코드가 어떻게 작성되었는지 살펴보겠습니다
$serv = new swoole_server("127.0.0.1", 9501); //设置异步任务的工作进程数量 $serv->set(array('task_worker_num' => 4)); $serv->on('receive', function($serv, $fd, $from_id, $data) { //投递异步任务 $task_id = $serv->task($data); echo "Dispath AsyncTask: id=$task_id\n"; }); //处理异步任务 $serv->on('task', function ($serv, $task_id, $from_id, $data) { echo "New AsyncTask[id=$task_id]".PHP_EOL; //返回任务执行的结果 $serv->finish("$data -> OK"); }); //处理异步任务的结果 $serv->on('finish', function ($serv, $task_id, $data) { echo "AsyncTask[$task_id] Finish: $data".PHP_EOL; }); $serv->start();
공식이 함수 익명 함수를 통해 후속 실행 로직을 전달하는 것을 볼 수 있습니다. 이렇게 보면 상황이 훨씬 단순해진다.
url = $url; } public function visitOneDegree() { $this->visit($this->url, function ($content) { $this->loadPage($content); $this->visitAll(); }); } private function loadPage($content) { $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url, $callBack = null) { $urlInfo = parse_url($url); Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo, $callBack) { if (!$ip) { return; } $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get($urlInfo['path'], function ($cli) use ($callBack) { if ($callBack) { call_user_func($callBack, $cli->body); } $cli->close(); }); }); } }
이 코드를 읽고 난 후, nodejs 개발에서 어디에서나 볼 수 있는 콜백에는 나름의 이유가 있다는 것을 느꼈습니다. 이제 나는 비동기 문제를 해결하기 위해 콜백이 존재한다는 것을 갑자기 이해합니다.
프로그램을 실행했는데 0.0007초밖에 안걸리고 시작하기도 전에 끝났어요! 비동기 효율성이 실제로 그렇게 많이 향상될 수 있습니까? 물론 대답은 '아니요'입니다. 코드에 문제가 있습니다.
비동기식 사용으로 인해 작업이 완전히 완료될 때까지 기다리지 않고 종료 시간을 계산하는 논리가 실행되었습니다. 이제 다시 콜백을 사용할 때가 된 것 같습니다.
/** Async/Crawler.php **/ public function visitOneDegree($callBack) { $this->visit($this->url, function ($content) use($callBack) { $this->loadPage($content); $this->visitAll(); call_user_func($callBack); }); }
<?php /** * crawler.php */ require_once 'Async/Crawler.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(function () use($start) { $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; }); /*output: time used: 0.068463802337646 */
지금 보면 결과가 훨씬 더 신빙성이 있어요.
동기화와 비동기화의 차이를 비교해 보겠습니다. 동기화는 6.26초가 걸리고 비동기화는 0.068초가 소요됩니다. 이는 전체 6.192초의 차이입니다. 아니, 좀 더 정확하게 말하면 거의 10배는 더 나빠질 것입니다!
물론 효율성 측면에서는 비동기 코드가 동기 코드보다 훨씬 높지만, 논리적으로 말하면 비동기 로직은 동기 코드보다 더 복잡하고 코드가 많은 콜백을 가져오기 때문에 이해하기 쉽지 않습니다.
Swoole 공식에는 매우 적절한 비동기식 및 동기식 선택에 대한 설명이 있습니다.
我们不赞成用异步回调的方式去做功能开发,传统的PHP同步方式实现功能和逻辑是最简单的,也是最佳的方案。像node.js这样到处callback,只是牺牲可维护性和开发效率。
관련 읽기:
How to install swoole Extension with php7
위 내용은 모두 이 글의 내용입니다. 학생들이 질문이 있는 경우 댓글 영역에서 토론할 수 있습니다. 아래~
위 내용은 Swoole을 사용하여 웹 페이지를 비동기식으로 크롤링하는 실제 공유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!