关于Curl在Swoole协程中的解决方案详析


Posted in PHP onSeptember 12, 2019

前言

众所周知,在 Swoole 应用中,是不推荐使用 Curl 的,因为 Curl 会阻塞进程。

本文会用实际的代码和数据,用最直观的方式,让你明白为什么。

最后还会给出 Curl 在 Swoole 中的解决方案,如果不想看分析可以直接拉到最后。

例程对比

宇润看文章不喜欢那些虚的,所以自己写也比较实在,直接来跑一下代码,用数据看为什么不推荐在 Swoole 使用 Curl。

为了偷懒,我直接用了 YurunHttp 的 Curl 和 Swoole Handler,来替代那些又臭又长的 Curl 代码。

代码
composer.json

{
  "require": {
    "yurunsoft/yurun-http": "~3.0"
  }
}

server.php

<?php
$http = new Swoole\Http\Server('127.0.0.1', 9501);
$http->on('workerstart', function(){
  \Swoole\Runtime::enableCoroutine();
});
$http->on('request', function ($request, $response) {
  sleep(1); // 假设各种处理耗时1秒
  $response->end($request->get['id'] . ': ' . date('Y-m-d H:i:s'));
});
$http->start();

test.php

<?php

use Yurun\Util\YurunHttp;
use Yurun\Util\HttpRequest;

require __DIR__ . '/vendor/autoload.php';

define('REQUEST_COUNT', 3);

go(function(){
  // 协程客户端
  echo 'coroutine http client:', PHP_EOL, PHP_EOL;
  $time = microtime(true);
  YurunHttp::setDefaultHandler(\Yurun\Util\YurunHttp\Handler\Swoole::class); // 切换为 Swoole Handler
  $channel = new \Swoole\Coroutine\Channel;
  for($i = 0; $i < REQUEST_COUNT; ++$i)
  {
    go(function() use($channel, $i){
      $http = new HttpRequest;
      $response = $http->get('http://127.0.0.1:9501/?id=' . $i); // 请求地址
      var_dump($response->body());
      $channel->push(1);
    });
  }
  for($i = 0; $i < REQUEST_COUNT; ++$i)
  {
    $channel->pop();
  }
  $channel->close();
  echo 'coroutine http client time: ', (microtime(true) - $time) . 's', PHP_EOL, PHP_EOL;

  // curl
  echo 'curl:', PHP_EOL, PHP_EOL;
  $time = microtime(true);
  YurunHttp::setDefaultHandler(\Yurun\Util\YurunHttp\Handler\Curl::class); // 切换为 Curl Handler
  $channel = new \Swoole\Coroutine\Channel;
  for($i = 0; $i < REQUEST_COUNT; ++$i)
  {
    go(function() use($channel, $i){
      $http = new HttpRequest;
      $response = $http->get('http://127.0.0.1:9501/?id=' . $i); // 请求地址
      var_dump($response->body());
      $channel->push(1);
    });
  }
  for($i = 0; $i < REQUEST_COUNT; ++$i)
  {
    $channel->pop();
  }
  $channel->close();
  echo 'curl time: ', (microtime(true) - $time) . 's', PHP_EOL, PHP_EOL;
});

运行

首次运行需要执行 composer update 安装依赖

运行 php server.php,启动服务端

运行 php test.php,启动客户端

运行结果

coroutine http client:

string(22) "1: 2019-09-11 08:35:54"
string(22) "0: 2019-09-11 08:35:54"
string(22) "2: 2019-09-11 08:35:54"
coroutine http client time: 1.0845630168915s

curl:

string(22) "0: 2019-09-11 08:35:55"
string(22) "1: 2019-09-11 08:35:56"
string(22) "2: 2019-09-11 08:35:57"
curl time: 3.0139901638031s

结果分析

上面的代码在服务端延迟 1 秒后返回结果,模拟实际业务的耗时。

通过客户端的耗时可以看出,Curl 3 次请求总共耗时 3 秒多,而协程客户端仅耗时 1 秒多。

因为前一次请求中,Curl 等待返回内容的时间是干不了其他事情的。而协程客户端等待返回内容期间,是挂起当前协程,转而再去执行其它协程中的代码。

解决方案

CoroutineHttpClient

使用 Swoole 内置的协程客户端实现,适合有一定基础的开发者使用。

文档:https://wiki.swoole.com/wiki/...

Guzzle-Swoole

我们在项目中,可能很少直接写 curl,但是用到的很多第三方类库(如某某云们的 SDK)会有用到。

这些第三方类库通常使用的是 Guzzle 作为 Http 客户端,而 Guzzle 底层也是使用 Curl 实现。

宇润专为此种场景研发了 Guzzle-Swoole 包,引入后可以让这些 SDK 轻松支持协程,而不用修改一行代码。

使用方法

执行命令直接安装依赖:composer require yurunsoft/guzzle-swoole ~1.1

全局设定处理器:

<?php
require dirname(__DIR__) . '/vendor/autoload.php';

use GuzzleHttp\Client;
use Yurun\Util\Swoole\Guzzle\SwooleHandler;
use GuzzleHttp\DefaultHandler;

DefaultHandler::setDefaultHandler(SwooleHandler::class);

go(function(){
  $client = new Client();
  $response = $client->request('GET', 'http://www.baidu.com', [
    'verify'  => false,
  ]);
  var_dump($response->getStatusCode());
});

手动指定 Swoole 处理器:

use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Yurun\Util\Swoole\Guzzle\SwooleHandler;

go(function(){
  $handler = new SwooleHandler();
  $stack = HandlerStack::create($handler);
  $client = new Client(['handler' => $stack]);
  $response = $client->request('GET', 'http://www.baidu.com', [
    'verify'  => false,
  ]);
  var_dump($response->getBody()->__toString(), $response->getHeaders());
});

YurunHttp

YurunHttp 是开源的PHP HTTP类库,支持链式操作,简单易用。

支持所有常见的GET、POST、PUT、DELETE、UPDATE等请求方式,支持浏览器级别 Cookies 管理、上传下载、设置和读取header、Cookie、请求参数、失败重试、限速、代理、证书等。

3.0 版完美支持Curl、Swoole 协程;3.2 版支持 Swoole WebSocket 客户端。

使用方法

执行命令直接安装依赖:composer require yurunsoft/yurun-http ~3.2

<?php
use Yurun\Util\YurunHttp;
use Yurun\Util\HttpRequest;

// 设置默认请求处理器为 Swoole
YurunHttp::setDefaultHandler(\Yurun\Util\YurunHttp\Handler\Swoole::class);

// Swoole 处理器必须在协程中调用
go('test');

function test()
{
  $http = new HttpRequest;
  $response = $http->get('http://www.baidu.com');
  echo 'html:', PHP_EOL, $response->body();
}

截止发稿时,Swoole 4.4 新增的 hook Curl 依然是实验性功能。虽然宇润曾为该功能贡献过一部分代码,但是由于需要兼容的工作量非常大,有太多 OPTION 不被支持,我个人是暂时不推荐使用 hook Curl 的。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
php 日期和时间的处理-郑阿奇(续)
Jul 04 PHP
PHP最常用的2种设计模式工厂模式和单例模式介绍
Aug 14 PHP
利用php绘制饼状图的实现代码
Jun 07 PHP
php中文验证码实现示例分享
Jan 12 PHP
浅谈php函数serialize()与unserialize()的使用方法
Aug 19 PHP
thinkphp3.2中Lite文件替换框架入口文件或应用入口文件的方法
May 21 PHP
摘自织梦CMS的HTTP文件下载类
Aug 08 PHP
php命令行(cli)模式下报require 加载路径错误的解决方法
Nov 23 PHP
WordPress的文章自动添加关键词及关键词的SEO优化
Mar 01 PHP
如何判断php mysqli扩展类是否开启
Dec 24 PHP
PHP设计模式之PHP迭代器模式讲解
Mar 22 PHP
如何用PHP websocket实现网页实时聊天
May 26 PHP
PHP判断一个变量是否为整数、正整数的方法示例
Sep 11 #PHP
PHP中有关长整数的一些操作教程
Sep 11 #PHP
PHP生成随机字符串实例代码(字母+数字)
Sep 11 #PHP
Yii框架参数配置文件params用法实例分析
Sep 11 #PHP
yii2.0框架使用 beforeAction 防非法登陆的方法分析
Sep 11 #PHP
Laravel框架验证码类用法实例分析
Sep 11 #PHP
PHP读取XML文件的方法实例总结【DOMDocument及simplexml方法】
Sep 10 #PHP
You might like
Zend Framework教程之Zend_Layout布局助手详解
2016/03/04 PHP
laravel框架实现后台登录、退出功能示例
2019/10/31 PHP
javascript页面上使用动态时间具体实现
2014/03/18 Javascript
扩展jQuery对象时如何扩展成员变量具体怎么实现
2014/04/25 Javascript
javascript在IE下trim函数无法使用的解决方法
2014/09/12 Javascript
js实现交换运动效果的方法
2015/04/10 Javascript
js插件设置innerHTML时在IE8下提示“未知运行时错误”解决方法
2015/04/25 Javascript
JavaScript实现级联菜单的方法
2015/06/29 Javascript
Jquery简单分页实现方法
2015/07/24 Javascript
仅一个form表单 js实现注册信息依次填写提交功能
2016/06/12 Javascript
基于Three.js插件制作360度全景图
2016/11/29 Javascript
jQuery插件之validation插件
2017/03/29 jQuery
javascript 判断一个对象为数组的方法
2017/05/03 Javascript
微信小程序 按钮滑动的实现方法
2017/09/27 Javascript
jQuery zTree搜索-关键字查询 递归无限层功能实现代码
2018/01/25 jQuery
记录一次完整的react hooks实践
2019/03/11 Javascript
通过JavaScript下载文件到本地的方法(单文件)
2019/03/17 Javascript
vue项目中使用fetch的实现方法
2019/04/25 Javascript
Vue表单之v-model绑定下拉列表功能
2019/05/14 Javascript
JavaScript实现Tab标签页切换的最简便方式(4种)
2020/06/28 Javascript
Python入门之modf()方法的使用
2015/05/15 Python
Django中传递参数到URLconf的视图函数中的方法
2015/07/18 Python
python装饰器与递归算法详解
2016/02/18 Python
pandas删除指定行详解
2019/04/04 Python
Python人脸识别第三方库face_recognition接口说明文档
2019/05/03 Python
python matplotlib模块基本图形绘制方法小结【直线,曲线,直方图,饼图等】
2020/04/26 Python
OpenCV 使用imread()函数读取图片的六种正确姿势
2020/07/09 Python
快速解决pymongo操作mongodb的时区问题
2020/12/05 Python
HomeAway澳大利亚:预订你的度假屋,公寓、度假村、别墅等
2019/02/20 全球购物
毕业研究生的自我鉴定
2013/11/30 职场文书
有多年工作经验的自我评价
2014/03/02 职场文书
超市周年庆活动方案
2014/08/16 职场文书
2014年超市员工工作总结
2014/11/18 职场文书
质检员岗位职责
2015/02/03 职场文书
2015年毕业生实习评语
2015/03/25 职场文书
学校标语口号大全
2015/12/26 职场文书