curl_close() 函数用于关闭一个cURL会话并释放所有资源。这个函数应该在cURL会话使用完毕后调用,以防止内存泄漏和资源浪费。
void curl_close ( resource $ch )
| 参数 | 描述 | 类型 | 必需 |
|---|---|---|---|
| ch | 由 curl_init() 返回的cURL句柄 |
resource | 是 |
没有返回值。
@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
@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
@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
@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句柄会占用系统资源,不及时关闭会导致内存使用量不断增加。
及时释放资源可以让系统更好地管理内存和网络连接。
在服务器环境中,未关闭的句柄可能达到系统限制,导致新请求失败。
显式资源管理使代码更清晰,易于维护和调试。
| 错误做法 | 问题描述 | 正确做法 |
|---|---|---|
| 忘记调用curl_close() | 导致内存泄漏,特别是在循环或长时间运行的脚本中 | 始终在cURL操作完成后调用curl_close() |
| 在错误情况下不关闭 | 当curl_exec()返回false时,仍然需要关闭句柄 | 使用try-finally或确保在所有路径上都关闭句柄 |
| 重复关闭句柄 | 对已关闭的句柄再次调用curl_close()可能产生警告 | 检查句柄是否仍然是资源类型再关闭 |
| 过早关闭句柄 | 在还需要使用句柄信息(如curl_getinfo())之前关闭 | 在获取所有需要的信息后再关闭句柄 |