PHP curl_close函数

定义和用法

curl_close() 函数用于关闭一个cURL会话并释放所有资源。这个函数应该在cURL会话使用完毕后调用,以防止内存泄漏和资源浪费。

提示: 虽然PHP会在脚本执行结束时自动释放资源,但显式调用curl_close()是一个良好的编程习惯,特别是在长时间运行的脚本中。

语法

void curl_close ( resource $ch )

参数

参数 描述 类型 必需
ch curl_init() 返回的cURL句柄 resource

返回值

没有返回值。

cURL完整工作流程

1
curl_init()
初始化会话
2
curl_setopt()
设置选项
3
curl_exec()
执行请求
4
curl_close()
关闭会话

示例

示例 1:基本的cURL使用流程

@php
// 1. 初始化cURL会话
$ch = curl_init();

// 2. 设置选项
curl_setopt_array($ch, [
    CURLOPT_URL => "https://api.example.com/data",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30
]);

// 3. 执行请求
$response = curl_exec($ch);

// 检查是否成功
if ($response === false) {
    echo "请求失败: " . curl_error($ch);
} else {
    echo "请求成功!响应长度: " . strlen($response) . " 字节";
}

// 4. 关闭会话(重要!)
curl_close($ch);

echo "cURL会话已关闭,资源已释放。";
@endphp

示例 2:使用try-finally确保资源释放

@php
function makeCurlRequestSafely($url, $options = []) {
    $ch = null;

    try {
        // 初始化cURL会话
        $ch = curl_init();

        // 设置默认选项
        $defaultOptions = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_FOLLOWLOCATION => true
        ];

        // 合并选项
        $finalOptions = $options + $defaultOptions;
        curl_setopt_array($ch, $finalOptions);

        // 执行请求
        $response = curl_exec($ch);

        if ($response === false) {
            throw new Exception('cURL请求失败: ' . curl_error($ch));
        }

        return $response;

    } catch (Exception $e) {
        // 记录错误日志
        error_log("cURL错误: " . $e->getMessage());
        throw $e;

    } finally {
        // 无论成功还是失败,都确保关闭cURL会话
        if ($ch !== null) {
            curl_close($ch);
            echo "cURL会话已安全关闭。\n";
        }
    }
}

// 使用安全的请求函数
try {
    $data = makeCurlRequestSafely("https://jsonplaceholder.typicode.com/posts/1");
    echo "请求成功: " . substr($data, 0, 100) . "...\n";
} catch (Exception $e) {
    echo "请求失败: " . $e->getMessage() . "\n";
}
@endphp

示例 3:资源管理与自动关闭

@php
class CurlManager {
    private $handles = [];

    public function createHandle($url, $options = []) {
        $ch = curl_init();

        $defaultOptions = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30
        ];

        $finalOptions = $options + $defaultOptions;
        curl_setopt_array($ch, $finalOptions);

        // 存储句柄以便后续管理
        $handleId = uniqid('curl_');
        $this->handles[$handleId] = $ch;

        return $handleId;
    }

    public function executeRequest($handleId) {
        if (!isset($this->handles[$handleId])) {
            throw new Exception("无效的cURL句柄: " . $handleId);
        }

        $ch = $this->handles[$handleId];
        $response = curl_exec($ch);

        if ($response === false) {
            $error = curl_error($ch);
            $this->closeHandle($handleId);
            throw new Exception("cURL请求失败: " . $error);
        }

        return $response;
    }

    public function closeHandle($handleId) {
        if (isset($this->handles[$handleId])) {
            curl_close($this->handles[$handleId]);
            unset($this->handles[$handleId]);
            echo "已关闭cURL句柄: {$handleId}\n";
        }
    }

    public function closeAllHandles() {
        foreach ($this->handles as $handleId => $ch) {
            curl_close($ch);
            echo "已关闭cURL句柄: {$handleId}\n";
        }
        $this->handles = [];
        echo "所有cURL会话已关闭。\n";
    }

    public function getActiveHandleCount() {
        return count($this->handles);
    }

    // 析构函数:确保对象销毁时关闭所有句柄
    public function __destruct() {
        $this->closeAllHandles();
    }
}

// 使用CurlManager管理多个请求
$manager = new CurlManager();

try {
    // 创建多个cURL句柄
    $handle1 = $manager->createHandle("https://httpbin.org/get");
    $handle2 = $manager->createHandle("https://httpbin.org/post", [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => "test=data"
    ]);

    echo "当前活跃句柄数: " . $manager->getActiveHandleCount() . "\n";

    // 执行请求
    $response1 = $manager->executeRequest($handle1);
    $response2 = $manager->executeRequest($handle2);

    echo "请求1成功,长度: " . strlen($response1) . " 字节\n";
    echo "请求2成功,长度: " . strlen($response2) . " 字节\n";

    // 手动关闭特定句柄
    $manager->closeHandle($handle1);
    echo "关闭后活跃句柄数: " . $manager->getActiveHandleCount() . "\n";

} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
} finally {
    // 确保关闭所有句柄
    $manager->closeAllHandles();
}
@endphp

示例 4:长时间运行脚本中的资源管理

@php
// 在长时间运行的脚本中(如守护进程、队列处理器),资源管理尤为重要

class LongRunningService {
    private $memoryLimit;
    private $requestCount = 0;

    public function __construct($memoryLimit = '128M') {
        $this->memoryLimit = $memoryLimit;
        // 设置内存限制
        ini_set('memory_limit', $memoryLimit);
    }

    public function processQueue($maxRequests = 100) {
        echo "开始处理队列,最大请求数: {$maxRequests}\n";
        echo "内存限制: " . ini_get('memory_limit') . "\n";

        for ($i = 0; $i < $maxRequests; $i++) {
            $this->requestCount++;

            // 监控内存使用
            $memoryUsage = memory_get_usage(true);
            $memoryUsageMB = round($memoryUsage / 1024 / 1024, 2);
            echo "请求 {$this->requestCount} - 内存使用: {$memoryUsageMB} MB\n";

            // 处理单个请求
            $this->processSingleRequest();

            // 定期检查内存使用,必要时进行垃圾回收
            if ($this->requestCount % 10 === 0) {
                $this->performCleanup();
            }

            // 模拟处理间隔
            sleep(1);
        }

        echo "队列处理完成,总共处理了 {$this->requestCount} 个请求\n";
    }

    private function processSingleRequest() {
        $ch = curl_init();

        try {
            curl_setopt_array($ch, [
                CURLOPT_URL => "https://httpbin.org/delay/1", // 模拟1秒延迟的API
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 5
            ]);

            $response = curl_exec($ch);

            if ($response !== false) {
                // 处理响应数据
                $data = json_decode($response, true);
                echo "请求成功: " . $data['url'] . "\n";
            } else {
                echo "请求失败: " . curl_error($ch) . "\n";
            }

        } catch (Exception $e) {
            echo "处理请求时发生异常: " . $e->getMessage() . "\n";

        } finally {
            // 关键:确保在每次请求后都关闭cURL会话
            if (is_resource($ch)) {
                curl_close($ch);
            }
        }
    }

    private function performCleanup() {
        echo "执行清理操作...\n";

        // 强制垃圾回收
        $collected = gc_collect_cycles();
        echo "垃圾回收器回收了 {$collected} 个循环引用\n";

        // 显示当前内存状态
        $memoryUsage = memory_get_usage(true);
        $peakMemory = memory_get_peak_usage(true);

        echo "当前内存: " . round($memoryUsage / 1024 / 1024, 2) . " MB\n";
        echo "峰值内存: " . round($peakMemory / 1024 / 1024, 2) . " MB\n";
    }
}

// 运行长时间服务
$service = new LongRunningService('256M');
$service->processQueue(5); // 处理5个请求作为演示
@endphp

为什么需要curl_close()?

防止内存泄漏

cURL句柄会占用系统资源,不及时关闭会导致内存使用量不断增加。

提高性能

及时释放资源可以让系统更好地管理内存和网络连接。

避免资源耗尽

在服务器环境中,未关闭的句柄可能达到系统限制,导致新请求失败。

良好编程习惯

显式资源管理使代码更清晰,易于维护和调试。

最佳实践

  • 始终调用curl_close() - 即使在发生错误的情况下也要确保调用
  • 使用try-finally模式 - 确保在异常情况下也能正确释放资源
  • 避免重复关闭 - 不要对同一个cURL句柄多次调用curl_close()
  • 及时关闭 - 在不再需要cURL句柄时立即关闭,不要等到脚本结束
  • 监控资源使用 - 在长时间运行的脚本中定期检查内存使用情况

常见错误

错误做法 问题描述 正确做法
忘记调用curl_close() 导致内存泄漏,特别是在循环或长时间运行的脚本中 始终在cURL操作完成后调用curl_close()
在错误情况下不关闭 当curl_exec()返回false时,仍然需要关闭句柄 使用try-finally或确保在所有路径上都关闭句柄
重复关闭句柄 对已关闭的句柄再次调用curl_close()可能产生警告 检查句柄是否仍然是资源类型再关闭
过早关闭句柄 在还需要使用句柄信息(如curl_getinfo())之前关闭 在获取所有需要的信息后再关闭句柄

注意事项

重要:
  • curl_close()调用后,cURL句柄将变为无效,不能再被使用
  • 关闭后的句柄不能再次用于任何cURL函数
  • 虽然PHP有垃圾回收机制,但不要依赖它来关闭cURL会话
  • 在长时间运行的脚本中,不关闭cURL句柄可能导致内存耗尽
  • 关闭句柄后,仍然可以访问之前curl_exec()返回的数据
  • 如果需要在关闭前获取连接信息,应在关闭前调用curl_getinfo()