编程学习网 > PHP技术 > php教程之从源码来看PHP中如何做好Curl参数设置
2021
08-03

php教程之从源码来看PHP中如何做好Curl参数设置

最近两天有一个Curl的问题出现。当高并发进来Curl会造成大面积超时,这个原因是参照第三方REST API(POST、PUT...)进行CURLOPT_TIMEOUT设置为2秒,结果造成大量的超时,这里的具体超时还没有去研究到底是链接数过多上行带宽资源占用还是下行带宽资源占用。因为这里涉及到链接数,如果A-B。B的链接数是根据IP、内存一些方式来限制的,理论上无限大。而A的链接数是根据端口、资源等等来限制的。这个还没有具体的进行测试。现在出现的问题就是超时,所以仔细研究一下超时代码。


$ch = curl_init();
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
$result = curl_exec($ch);
curl_close($ch);
return $result;
php源码



PHP_FUNCTION(curl_init)
{
  php_curl *ch;
  CURL    *cp;
  char    *url = NULL;
  size_t      url_len = 0;

  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &url, &url_len) == FAILURE) {
    return;
  }

  cp = curl_easy_init();
  if (!cp) {
    php_error_docref(NULL, E_WARNING, "Could not initialize a new cURL handle");
    RETURN_FALSE;
  }

  ch = alloc_curl_handle();

  ch->cp = cp;

  ch->handlers->write->method = PHP_CURL_STDOUT;
  ch->handlers->read->method  = PHP_CURL_DIRECT;
  ch->handlers->write_header->method = PHP_CURL_IGNORE;

  _php_curl_set_default_options(ch);

  if (url) {
    if (php_curl_option_url(ch, url, url_len) == FAILURE) {
      _php_curl_close_ex(ch);
      RETURN_FALSE;
    }
  }

  ZVAL_RES(return_value, zend_register_resource(ch, le_curl));
  ch->res = Z_RES_P(return_value);
}
有一些配置还有一个CURL原生指针,结构体如下。
typedef struct {
  CURL                         *cp;
  php_curl_handlers            *handlers;
  zend_resource                *res;
  struct _php_curl_free        *to_free;
  struct _php_curl_send_headers header;
  struct _php_curl_error        err;
  zend_bool                     in_callback;
  uint32_t*                     clone;
} php_curl;

cp指向的是原生句柄(/usr/include/curl),这里有两个初始化函数,一个是PHP自带的,一个是CURL自带的。

alloc_curl_handle

static php_curl *alloc_curl_handle()
{
  //申请内存
  php_curl *ch               = ecalloc(1, sizeof(php_curl));
  ch->to_free                = ecalloc(1, sizeof(struct _php_curl_free));
  ch->handlers               = ecalloc(1, sizeof(php_curl_handlers));
  ch->handlers->write        = ecalloc(1, sizeof(php_curl_write));
  ch->handlers->write_header = ecalloc(1, sizeof(php_curl_write));
  ch->handlers->read         = ecalloc(1, sizeof(php_curl_read));
  ch->handlers->progress     = NULL;
#if LIBCURL_VERSION_NUM >= 0x071500 /* Available since 7.21.0 */
  ch->handlers->fnmatch      = NULL;
#endif
  ch->clone            = emalloc(sizeof(uint32_t));
  *ch->clone                 = 1;

  memset(&ch->err, 0, sizeof(struct _php_curl_error));

  zend_llist_init(&ch->to_free->str,   sizeof(char *),          (llist_dtor_func_t)curl_free_string, 0);
  zend_llist_init(&ch->to_free->post,  sizeof(struct HttpPost *), (llist_dtor_func_t)curl_free_post,   0);

  ch->to_free->slist = emalloc(sizeof(HashTable));
  zend_hash_init(ch->to_free->slist, 4, NULL, curl_free_slist, 0);

  return ch;
}

大致上都是申请内存,这里有一个ecalloc这个是对memset的一个安全封装。基本上初始化就是申请一些内存,然后调用CURL的curl_easy_init进行初始化句柄,curl_easy_init内部也是申请内存,不存在复用情况。其中有一个默认配置函数。

static void _php_curl_set_default_options(php_curl *ch)
{
    char *cainfo;

    curl_easy_setopt(ch->cp, CURLOPT_NOPROGRESS,        1);
    curl_easy_setopt(ch->cp, CURLOPT_VERBOSE,           0);
    curl_easy_setopt(ch->cp, CURLOPT_ERRORBUFFER,       ch->err.str);
    curl_easy_setopt(ch->cp, CURLOPT_WRITEFUNCTION,     curl_write);
    curl_easy_setopt(ch->cp, CURLOPT_FILE,              (void *) ch);
    curl_easy_setopt(ch->cp, CURLOPT_READFUNCTION,      curl_read);
    curl_easy_setopt(ch->cp, CURLOPT_INFILE,            (void *) ch);
    curl_easy_setopt(ch->cp, CURLOPT_HEADERFUNCTION,    curl_write_header);
    curl_easy_setopt(ch->cp, CURLOPT_WRITEHEADER,       (void *) ch);
#if !defined(ZTS)
    curl_easy_setopt(ch->cp, CURLOPT_DNS_USE_GLOBAL_CACHE, 1);
#endif
    curl_easy_setopt(ch->cp, CURLOPT_DNS_CACHE_TIMEOUT, 120);
    curl_easy_setopt(ch->cp, CURLOPT_MAXREDIRS, 20); /* prevent infinite redirects */

    cainfo = INI_STR("openssl.cafile");
    if (!(cainfo && cainfo[0] != '\0')) {
        cainfo = INI_STR("curl.cainfo");
    }
    if (cainfo && cainfo[0] != '\0') {
        curl_easy_setopt(ch->cp, CURLOPT_CAINFO, cainfo);
    }

#if defined(ZTS)
    curl_easy_setopt(ch->cp, CURLOPT_NOSIGNAL, 1);
#endif
}

从默认配置也可以看出实际对于这个TIMEOUT只有一个DNS缓存的配置是120秒。并没有对TIMEOUT进行设置。在来看一下关于参数设置的函数

static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{ */
{
    CURLcode error = CURLE_OK;
    zend_long lval;

    ZVAL_DEREF(zvalue);
    switch (option) {
        
        ......

        case CURLOPT_TIMECONDITION:
        case CURLOPT_TIMEOUT:
        case CURLOPT_TIMEVALUE:
        case CURLOPT_TRANSFERTEXT:
        case CURLOPT_UNRESTRICTED_AUTH:
        case CURLOPT_UPLOAD:
        case CURLOPT_VERBOSE:
#if LIBCURL_VERSION_NUM >= 0x070a06 /* Available since 7.10.6 */
        case CURLOPT_HTTPAUTH:
#endif
#if LIBCURL_VERSION_NUM >= 0x070a07 /* Available since 7.10.7 */
        case CURLOPT_FTP_CREATE_MISSING_DIRS:
        case CURLOPT_PROXYAUTH:
#endif
#if LIBCURL_VERSION_NUM >= 0x070a08 /* Available since 7.10.8 */
        case CURLOPT_FTP_RESPONSE_TIMEOUT:
        case CURLOPT_IPRESOLVE:
        case CURLOPT_MAXFILESIZE:
#endif
#if CURLOPT_MUTE != 0
        case CURLOPT_MUTE:
  #endif
            lval = zval_get_long(zvalue);
#if LIBCURL_VERSION_NUM >= 0x71304
            if ((option == CURLOPT_PROTOCOLS || option == CURLOPT_REDIR_PROTOCOLS) &&
                (PG(open_basedir) && *PG(open_basedir)) && (lval & CURLPROTO_FILE)) {
                    php_error_docref(NULL, E_WARNING, "CURLPROTO_FILE cannot be activated when an open_basedir is set");
                    return 1;
            }
#endif
# if defined(ZTS)
            if (option == CURLOPT_DNS_USE_GLOBAL_CACHE) {
                php_error_docref(NULL, E_WARNING, "CURLOPT_DNS_USE_GLOBAL_CACHE cannot be activated when thread safety is enabled");
                return 1;
            }
# endif
            error = curl_easy_setopt(ch->cp, option, lval);
            break;

            ......

截取了函数的一部分,也可以看到关于CURLOPT_TIMEOUT实际并没有默认设置,如果你进行了配置CURLOPT_TIMEOUT才会实际有效。这里我也全局搜索了一下CURLOPT_TIMEOUT。只有上述函数还有REGISTER_CURL_CONSTANT有用到。不过REGISTER_CURL_CONSTANT本来就是用来注册常量到模块的。

ZEND_API void zend_register_long_constant(const char *name, size_t name_len, zend_long lval, int flags, int module_number)
{
    zend_constant c;

    ZVAL_LONG(&c.value, lval);
    c.flags = flags;
    c.name = zend_string_init(name, name_len, flags & CONST_PERSISTENT);
    c.module_number = module_number;
    zend_register_constant(&c);
}

所以这个TIMEOUT如果不设置,那么它应该是跟着LibCurl走,这里在看一下LibCurl。

Default timeout is 0 (zero) which means it never times out during transfer.

意思就是默认是0,也就是传输过程永远不会超时。也就是说TIMEOUT指的是传输过程,其中还有一个连接过程:CURLOPT_CONNECTTIMEOUT。这个过程的默认时间是300秒,也就是5分钟。

以上就是“php教程之从源码来看PHP中如何做好Curl参数设置”的详细内容,想要获取更多php教程欢迎关注编程学习网


扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取