curl_multi_getcontent() 函数用于获取cURL多句柄中单个句柄的响应内容。这个函数在并行请求处理完成后,用于提取每个请求的返回数据。
string curl_multi_getcontent ( resource $ch )
| 参数 | 描述 | 类型 | 必需 |
|---|---|---|---|
| ch | 由 curl_init() 返回的cURL句柄,该句柄必须已添加到多句柄并执行完成 |
resource | 是 |
返回cURL句柄的响应内容字符串。如果请求失败或没有设置CURLOPT_RETURNTRANSFER选项,返回FALSE。
@php
// 创建多句柄
$mh = curl_multi_init();
// 创建多个cURL句柄
$handles = [];
$urls = [
'https://httpbin.org/json',
'https://httpbin.org/xml',
'https://httpbin.org/html'
];
foreach ($urls as $i => $url) {
$handles[$i] = curl_init();
curl_setopt_array($handles[$i], [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true, // 必须设置为true才能获取内容
CURLOPT_TIMEOUT => 30
]);
curl_multi_add_handle($mh, $handles[$i]);
}
// 执行并行请求
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh, 0.1);
} while ($running > 0);
echo "所有请求执行完成,开始获取内容...\n\n";
// 获取每个请求的内容
foreach ($handles as $i => $handle) {
// 使用curl_multi_getcontent获取响应内容
$content = curl_multi_getcontent($handle);
if ($content !== false) {
$info = curl_getinfo($handle);
$contentType = $info['content_type'] ?? 'unknown';
$contentLength = strlen($content);
echo "请求 {$i} 内容获取成功:\n";
echo " URL: {$urls[$i]}\n";
echo " 内容类型: {$contentType}\n";
echo " 内容长度: {$contentLength} 字节\n";
echo " 前100字符: " . substr($content, 0, 100) . "...\n\n";
} else {
$error = curl_error($handle);
echo "请求 {$i} 内容获取失败: {$error}\n\n";
}
// 清理资源
curl_multi_remove_handle($mh, $handle);
curl_close($handle);
}
curl_multi_close($mh);
@endphp
@php
class RealTimeContentProcessor {
private $multiHandle;
private $handles = [];
private $results = [];
public function __construct() {
$this->multiHandle = curl_multi_init();
}
public function addRequest($url, $id = null) {
if ($id === null) {
$id = uniqid('req_');
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true, // 关键:必须设置为true
CURLOPT_TIMEOUT => 30
]);
$this->handles[$id] = $ch;
curl_multi_add_handle($this->multiHandle, $ch);
return $id;
}
public function execute() {
echo "开始执行并行请求...\n";
$active = null;
do {
// 执行多句柄
$status = curl_multi_exec($this->multiHandle, $active);
// 实时处理已完成的请求
$this->processCompletedRequests();
// 等待活动
if ($active > 0) {
curl_multi_select($this->multiHandle, 0.1);
}
} while ($active > 0);
// 处理可能剩余的任何请求
$this->processCompletedRequests(true);
echo "所有请求处理完成!\n";
return $this->results;
}
private function processCompletedRequests($final = false) {
// 处理所有已完成的请求
while ($info = curl_multi_info_read($this->multiHandle)) {
$handle = $info['handle'];
$id = array_search($handle, $this->handles, true);
if ($id !== false) {
$this->processSingleContent($id, $handle, $info);
unset($this->handles[$id]);
}
}
}
private function processSingleContent($id, $handle, $info) {
// 使用curl_multi_getcontent获取响应内容
$content = curl_multi_getcontent($handle);
$curlInfo = curl_getinfo($handle);
$error = curl_error($handle);
$result = [
'success' => ($info['result'] === CURLE_OK && $content !== false),
'content' => $content,
'info' => $curlInfo,
'error' => $error,
'content_length' => strlen($content),
'processed_at' => microtime(true)
];
// 根据内容类型进行不同处理
$this->processContentByType($id, $result);
// 存储结果
$this->results[$id] = $result;
// 从多句柄中移除
curl_multi_remove_handle($this->multiHandle, $handle);
curl_close($handle);
echo "请求 {$id} 处理完成 (" . round($curlInfo['total_time'], 2) . "秒)\n";
}
private function processContentByType($id, &$result) {
$contentType = $result['info']['content_type'] ?? '';
$content = $result['content'];
if (strpos($contentType, 'application/json') !== false) {
// JSON内容处理
$data = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE) {
$result['parsed_data'] = $data;
$result['data_type'] = 'json';
echo " [{$id}] JSON数据解析成功,包含 " . count($data) . " 个字段\n";
} else {
$result['parse_error'] = 'JSON解析失败: ' . json_last_error_msg();
}
} elseif (strpos($contentType, 'application/xml') !== false || strpos($contentType, 'text/xml') !== false) {
// XML内容处理
$xml = simplexml_load_string($content);
if ($xml !== false) {
$result['parsed_data'] = $xml;
$result['data_type'] = 'xml';
echo " [{$id}] XML数据解析成功\n";
} else {
$result['parse_error'] = 'XML解析失败';
}
} elseif (strpos($contentType, 'text/html') !== false) {
// HTML内容处理
$result['data_type'] = 'html';
$title = preg_match('/ <title>(.*?)<\/title>/i', $content, $matches) ? $matches[1] : '无标题';
$result['page_title'] = $title;
echo " [{$id}] HTML页面标题: {$title}\n";
} else {
$result['data_type'] = 'raw';
echo " [{$id}] 原始内容,类型: {$contentType}\n";
}
}
public function getResults() {
return $this->results;
}
public function close() {
foreach ($this->handles as $handle) {
if (is_resource($handle)) {
curl_multi_remove_handle($this->multiHandle, $handle);
curl_close($handle);
}
}
if (is_resource($this->multiHandle)) {
curl_multi_close($this->multiHandle);
}
$this->handles = [];
$this->results = [];
}
public function __destruct() {
$this->close();
}
}
// 使用实时内容处理器
$processor = new RealTimeContentProcessor();
// 添加不同类型的API请求
$requests = [
'json_api' => 'https://httpbin.org/json',
'xml_api' => 'https://httpbin.org/xml',
'html_page' => 'https://httpbin.org/html',
'uuid_api' => 'https://httpbin.org/uuid'
];
foreach ($requests as $id => $url) {
$processor->addRequest($url, $id);
}
// 执行并处理所有请求
$results = $processor->execute();
// 显示统计信息
echo "\n内容处理统计:\n";
$successCount = 0;
$totalSize = 0;
foreach ($results as $id => $result) {
if ($result['success']) {
$successCount++;
$totalSize += $result['content_length'];
echo "✓ {$id}: {$result['data_type']}, {$result['content_length']} 字节\n";
} else {
echo "✗ {$id}: 失败 - {$result['error']}\n";
}
}
echo "\n总结: {$successCount}/" . count($requests) . " 成功, 总数据量: " . round($totalSize / 1024, 2) . " KB\n";
$processor->close();
@endphp
@php
class LargeContentHandler {
private $multiHandle;
private $handles = [];
private $outputDir;
public function __construct($outputDir = null) {
$this->multiHandle = curl_multi_init();
$this->outputDir = $outputDir ?: sys_get_temp_dir() . '/curl_downloads';
// 创建输出目录
if (!is_dir($this->outputDir)) {
mkdir($this->outputDir, 0755, true);
}
}
public function addDownload($url, $filename = null, $id = null) {
if ($id === null) {
$id = uniqid('dl_');
}
if ($filename === null) {
$filename = $id . '_' . basename(parse_url($url, PHP_URL_PATH) ?: 'download');
}
$outputPath = $this->outputDir . '/' . $filename;
$ch = curl_init();
// 对于大文件,使用文件流而不是内存存储
$fileHandle = fopen($outputPath, 'w');
if ($fileHandle === false) {
throw new Exception("无法创建文件: {$outputPath}");
}
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_FILE => $fileHandle, // 直接写入文件,避免内存占用
CURLOPT_TIMEOUT => 60,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_NOPROGRESS => false,
CURLOPT_PROGRESSFUNCTION => function($resource, $download_size, $downloaded, $upload_size, $uploaded) use ($id) {
if ($download_size > 0) {
$percent = round(($downloaded / $download_size) * 100);
echo "下载进度 [{$id}]: {$percent}% ({$downloaded}/{$download_size} 字节)\n";
}
return 0;
}
]);
$this->handles[$id] = [
'handle' => $ch,
'file_handle' => $fileHandle,
'output_path' => $outputPath,
'url' => $url
];
curl_multi_add_handle($this->multiHandle, $ch);
return $id;
}
public function execute() {
echo "开始并行下载...\n";
echo "输出目录: {$this->outputDir}\n\n";
$startMemory = memory_get_usage(true);
$active = null;
do {
$status = curl_multi_exec($this->multiHandle, $active);
// 处理完成的下载
$this->processCompletedDownloads();
if ($active > 0) {
curl_multi_select($this->multiHandle, 0.1);
}
// 监控内存使用
$currentMemory = memory_get_usage(true);
$memoryUsageMB = round($currentMemory / 1024 / 1024, 2);
echo "当前内存使用: {$memoryUsageMB} MB\n";
} while ($active > 0);
$this->processCompletedDownloads(true);
$endMemory = memory_get_usage(true);
$memoryIncrease = round(($endMemory - $startMemory) / 1024 / 1024, 2);
echo "\n下载完成!内存增加: {$memoryIncrease} MB\n";
return $this->getDownloadResults();
}
private function processCompletedDownloads($final = false) {
while ($info = curl_multi_info_read($this->multiHandle)) {
$handle = $info['handle'];
$id = $this->findDownloadId($handle);
if ($id !== null) {
$this->finalizeDownload($id, $handle, $info);
}
}
}
private function findDownloadId($handle) {
foreach ($this->handles as $id => $data) {
if ($data['handle'] === $handle) {
return $id;
}
}
return null;
}
private function finalizeDownload($id, $handle, $info) {
$downloadData = $this->handles[$id];
// 关闭文件句柄
if (is_resource($downloadData['file_handle'])) {
fclose($downloadData['file_handle']);
}
$curlInfo = curl_getinfo($handle);
$error = curl_error($handle);
$fileSize = file_exists($downloadData['output_path']) ? filesize($downloadData['output_path']) : 0;
$result = [
'success' => ($info['result'] === CURLE_OK && $fileSize > 0),
'file_path' => $downloadData['output_path'],
'file_size' => $fileSize,
'url' => $downloadData['url'],
'error' => $error,
'http_code' => $curlInfo['http_code'],
'download_time' => $curlInfo['total_time']
];
// 从多句柄中移除
curl_multi_remove_handle($this->multiHandle, $handle);
curl_close($handle);
unset($this->handles[$id]);
$status = $result['success'] ? '成功' : '失败';
$size = round($fileSize / 1024, 2) . ' KB';
echo "下载 {$id}: {$status}, 文件大小: {$size}, 耗时: " . round($result['download_time'], 2) . "秒\n";
return $result;
}
private function getDownloadResults() {
$results = [];
// 处理可能剩余的任何句柄
foreach ($this->handles as $id => $data) {
if (is_resource($data['file_handle'])) {
fclose($data['file_handle']);
}
$fileSize = file_exists($data['output_path']) ? filesize($data['output_path']) : 0;
$results[$id] = [
'success' => false,
'file_path' => $data['output_path'],
'file_size' => $fileSize,
'url' => $data['url'],
'error' => '未完成下载'
];
curl_multi_remove_handle($this->multiHandle, $data['handle']);
curl_close($data['handle']);
}
$this->handles = [];
return $results;
}
public function close() {
foreach ($this->handles as $data) {
if (is_resource($data['handle'])) {
curl_multi_remove_handle($this->multiHandle, $data['handle']);
curl_close($data['handle']);
}
if (is_resource($data['file_handle'])) {
fclose($data['file_handle']);
}
}
if (is_resource($this->multiHandle)) {
curl_multi_close($this->multiHandle);
}
$this->handles = [];
}
public function __destruct() {
$this->close();
}
}
// 使用大内容处理器下载文件
$downloader = new LargeContentHandler();
// 添加多个文件下载(使用小文件作为示例)
$downloads = [
'image1' => ['url' => 'https://httpbin.org/image/jpeg', 'filename' => 'sample1.jpg'],
'image2' => ['url' => 'https://httpbin.org/image/png', 'filename' => 'sample2.png'],
'json_data' => ['url' => 'https://httpbin.org/json', 'filename' => 'data.json']
];
foreach ($downloads as $id => $download) {
$downloader->addDownload($download['url'], $download['filename'], $id);
}
$results = $downloader->execute();
// 显示下载结果
echo "\n下载结果:\n";
foreach ($results as $id => $result) {
if ($result['success']) {
echo "✓ {$id}: 下载成功, 保存到: {$result['file_path']}\n";
} else {
echo "✗ {$id}: 下载失败 - {$result['error']}\n";
}
}
$downloader->close();
@endphp
@php
class CachedContentFetcher {
private $multiHandle;
private $cacheDir;
private $cacheTtl;
public function __construct($cacheDir = null, $cacheTtl = 3600) {
$this->multiHandle = curl_multi_init();
$this->cacheDir = $cacheDir ?: sys_get_temp_dir() . '/curl_cache';
$this->cacheTtl = $cacheTtl;
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
public function fetchUrls($urls, $forceRefresh = false) {
$results = [];
$toFetch = [];
echo "开始处理 " . count($urls) . " 个URL...\n";
// 检查缓存
foreach ($urls as $id => $url) {
$cacheKey = $this->getCacheKey($url);
$cacheFile = $this->cacheDir . '/' . $cacheKey;
if (!$forceRefresh && $this->isCacheValid($cacheFile)) {
// 使用缓存内容
$cachedContent = file_get_contents($cacheFile);
$cacheInfo = json_decode(file_get_contents($cacheFile . '.meta'), true);
$results[$id] = [
'source' => 'cache',
'content' => $cachedContent,
'cached_at' => $cacheInfo['cached_at'],
'url' => $url
];
echo "✓ {$id}: 使用缓存 (" . round(strlen($cachedContent) / 1024, 2) . " KB)\n";
} else {
// 需要重新获取
$toFetch[$id] = $url;
}
}
echo "缓存命中: " . (count($urls) - count($toFetch)) . "/" . count($urls) . "\n";
// 获取未缓存的内容
if (!empty($toFetch)) {
$fetchResults = $this->fetchMultiple($toFetch);
// 缓存新获取的内容
foreach ($fetchResults as $id => $result) {
if ($result['success']) {
$this->saveToCache($result['url'], $result['content']);
$result['source'] = 'network';
}
$results[$id] = $result;
}
}
return $results;
}
private function fetchMultiple($urls) {
$handles = [];
$results = [];
echo "开始并行获取 " . count($urls) . " 个URL...\n";
// 创建并添加句柄
foreach ($urls as $id => $url) {
$handles[$id] = curl_init();
curl_setopt_array($handles[$id], [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_FOLLOWLOCATION => true
]);
curl_multi_add_handle($this->multiHandle, $handles[$id]);
}
// 执行并行请求
$active = null;
do {
curl_multi_exec($this->multiHandle, $active);
curl_multi_select($this->multiHandle, 0.1);
} while ($active > 0);
// 获取内容
foreach ($handles as $id => $handle) {
$content = curl_multi_getcontent($handle);
$info = curl_getinfo($handle);
$error = curl_error($handle);
$results[$id] = [
'success' => ($content !== false),
'content' => $content,
'url' => $urls[$id],
'info' => $info,
'error' => $error,
'content_length' => strlen($content)
];
curl_multi_remove_handle($this->multiHandle, $handle);
curl_close($handle);
$status = $results[$id]['success'] ? '成功' : '失败';
echo " {$id}: {$status} (" . round($info['total_time'], 2) . "秒)\n";
}
return $results;
}
private function getCacheKey($url) {
return md5($url) . '.cache';
}
private function isCacheValid($cacheFile) {
if (!file_exists($cacheFile) || !file_exists($cacheFile . '.meta')) {
return false;
}
$cacheTime = filemtime($cacheFile);
return (time() - $cacheTime) < $this->cacheTtl;
}
private function saveToCache($url, $content) {
$cacheKey = $this->getCacheKey($url);
$cacheFile = $this->cacheDir . '/' . $cacheKey;
// 保存内容
file_put_contents($cacheFile, $content);
// 保存元数据
$meta = [
'url' => $url,
'cached_at' => time(),
'content_length' => strlen($content),
'content_type' => 'unknown' // 在实际应用中可以从curl信息获取
];
file_put_contents($cacheFile . '.meta', json_encode($meta));
}
public function clearCache($olderThan = null) {
$files = glob($this->cacheDir . '/*.cache');
$cleared = 0;
foreach ($files as $file) {
if ($olderThan === null || (time() - filemtime($file)) > $olderThan) {
unlink($file);
unlink($file . '.meta'); // 同时删除元数据文件
$cleared++;
}
}
return $cleared;
}
public function getCacheStats() {
$files = glob($this->cacheDir . '/*.cache');
$totalSize = 0;
foreach ($files as $file) {
$totalSize += filesize($file);
}
return [
'file_count' => count($files),
'total_size' => $totalSize,
'total_size_mb' => round($totalSize / 1024 / 1024, 2)
];
}
public function close() {
if (is_resource($this->multiHandle)) {
curl_multi_close($this->multiHandle);
}
}
public function __destruct() {
$this->close();
}
}
// 使用缓存内容获取器
$cacheDir = sys_get_temp_dir() . '/demo_curl_cache';
$fetcher = new CachedContentFetcher($cacheDir, 300); // 5分钟缓存
// 要获取的URL列表
$urls = [
'api1' => 'https://httpbin.org/json',
'api2' => 'https://httpbin.org/xml',
'api3' => 'https://httpbin.org/uuid',
'api4' => 'https://httpbin.org/headers'
];
echo "=== 第一次获取(将缓存内容)===\n";
$results1 = $fetcher->fetchUrls($urls);
echo "\n=== 第二次获取(应使用缓存)===\n";
$results2 = $fetcher->fetchUrls($urls);
// 显示缓存统计
$stats = $fetcher->getCacheStats();
echo "\n缓存统计:\n";
echo "缓存文件数: " . $stats['file_count'] . "\n";
echo "缓存总大小: " . $stats['total_size_mb'] . " MB\n";
// 显示结果来源统计
$sourceCounts = ['cache' => 0, 'network' => 0];
foreach ($results2 as $result) {
$sourceCounts[$result['source']]++;
}
echo "\n第二次获取来源:\n";
echo "缓存: {$sourceCounts['cache']}\n";
echo "网络: {$sourceCounts['network']}\n";
// 清理缓存示例
// $cleared = $fetcher->clearCache(60); // 清理1分钟前的缓存
// echo "清理了 {$cleared} 个缓存文件\n";
$fetcher->close();
@endphp
必须设置CURLOPT_RETURNTRANSFER = true,否则curl_multi_getcontent()将返回FALSE。
必须在请求执行完成后调用,通常在curl_multi_exec()循环结束后或使用curl_multi_info_read()检测到请求完成时。
对于大文件,考虑使用CURLOPT_FILE直接写入文件,避免内存占用过高。
获取内容后应及时使用curl_multi_remove_handle()和curl_close()清理资源。
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 返回 FALSE | 未设置 CURLOPT_RETURNTRANSFER 或请求未完成 | 确保设置 CURLOPT_RETURNTRANSFER = true 并在请求完成后调用 |
| 内存耗尽 | 同时处理大量大文件内容 | 使用 CURLOPT_FILE 直接写入文件,或限制并发数量 |
| 内容不完整 | 在请求完成前调用函数 | 使用 curl_multi_info_read() 确保请求已完成 |
| 句柄无效 | 句柄已被关闭或移除 | 在调用 curl_multi_getcontent() 前不要关闭句柄 |