php用curl下载的断点续传实现方式

发了几个文章,但都没人评论,所以哥也不知道到底大家觉得是好是坏,欢迎给差评,这样才能提高啊亲!踊跃回复吧!


ND公司的内网网络非常的差,对于我们PHPer工作人员来说有时真是噩梦,但这场噩梦没那么容易醒呵呵。


前段时间要采集应用包,算出MD5,以后方便做病毒比对,但是每个包都只能下载到 6MB 左右就断了,我去年买了个表啊!还好网络搜索是强大的!经过一翻折腾还是让我搞定了这件事,于是有了这篇教程,跟大家分享下,代码在TP框架上写的,所以部分代码如果你们不懂,请翻翻TP手册,很简单的。让我们开始吧:


(转载请注明出处:福州 郑洪耕 QQ_823339113)


网上关于php实现断点续传,一般是用 header 函数设置 range 来实现,代码大概是下面这样的:

	Header("Content-Length: " . ($fsize - $start));
	Header("Content-Ranges: bytes" . $start . "-" . ($fsize - 1) . "/" . $fsize);

但有时不得不用curl来下载。看了curl手册发现个参数:CURLOPT_RESUME_FROM 可以设置从哪个偏移地址开始下载,所以就有了实现思路如下:


先检测下载资源是否存在,存在时 --> 开始下载,然后查看返回结果 --> 返回结果为失败时,取得这次已经下载文件片段的大小,然后做为偏移量继续用curl下载 --> 保存每个下载的文件的文件片段名字,在全部下载完成后合并文件。


实现过程还用到了哥自己实现的进程锁机制,大家也讨论下有没有更好的方案。


过程就是这样了,先准备好几个函数,用来检测资源是否存在(http状态)、用CURL下载资源、合并下载的文件片段:


function downUrl($url, $saveFile, $offset = 0, $proxy = '') {
	$h_curl = curl_init();
	$h_file = fopen($saveFile, 'wb');
	curl_setopt($h_curl, CURLOPT_HEADER, 0);
	curl_setopt($h_curl, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt($h_curl, CURLOPT_TIMEOUT, 60);
	curl_setopt($h_curl, CURLOPT_URL, $url);
	curl_setopt($h_curl, CURLOPT_FILE, $h_file);
	curl_setopt($h_curl, CURLOPT_PROXY, $proxy);
	curl_setopt($h_curl, CURLOPT_SSL_VERIFYPEER, false); // 阻止对证书的合法性的检查
	curl_setopt($h_curl, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
	curl_setopt($h_curl, CURLOPT_RESUME_FROM, $offset);
	//curl_setopt($h_curl, CURLOPT_RETURNTRANSFER, true);
	$curl_success = curl_exec($h_curl);

	fclose($h_file);
	curl_close($h_curl);
	return $curl_success;
}

//合并两个文件,保存到$mainFile
function mergeFile($mainFile, $partFile) {
	$h_mainFile = fopen($mainFile, 'ab+');
	$h_partFile = fopen($partFile, 'rb');
	$content = fread($h_partFile, filesize($partFile));
	fclose($h_partFile);
	fwrite($h_mainFile, $content);
	fclose($h_mainFile);
}

function getHttpStatus($p_url) {
	$h_curl = curl_init();
	$url = trim($p_url);
	curl_setopt($h_curl, CURLOPT_URL, $url);
	curl_setopt($h_curl, CURLOPT_HEADER, 1); //获取Header
	curl_setopt($h_curl, CURLOPT_NOBODY,true); //Body不要
	curl_setopt($h_curl, CURLOPT_RETURNTRANSFER, 1); //数据存到字符串,别直接输出到屏幕
	$data = curl_exec($h_curl);
	$statCode = curl_getinfo($h_curl, CURLINFO_HTTP_CODE); //HTTPSTAT码
	curl_close($h_curl);

	return $statCode;
}

然后就是核心代码部分了(这是TP里的一个控制器,涉及的TP知识其实不多,稍微查下手册就知道了):

<?php
/**
 *
 * 更新App的MD5值
 *
 * @author UID 2013.04.26
 *
 */
class AppHashAction extends Action{

	//App 表里没有MD5值、是外链的,下载软件包计算MD5值
	private $md5FileToWrite;

	public function _initialize() {
		$this->md5FileToWrite = BASE_PATH . '/urlmd5.txt';
		@rename($this->md5FileToWrite,
				$this->md5FileToWrite . $_SERVER['REQUEST_TIME']);
		@rename($this->md5FileToWrite . '.php',
				$this->md5FileToWrite . '.php' . $_SERVER['REQUEST_TIME']);
	}

	public function index() {
		$lockFile = BASE_PATH . '/stop.lock';
		$mMd5 = M('AppMd5');
		$where = array(
			'app_md5' => array('eq', ''),
		);
		$field = 'app_id,app_url';
		$order = 'app_size asc';
		$appList = $mMd5->where($where)->field($field)->order($order)->select();

		$this->_finish_request(0);
		foreach($appList as $idx => $app) {
			if(file_exists($lockFile)) {//进程锁机制,可以防多进程跑同一任务,也可以在中途删除锁文件,让进程停止(当然主要目的是要它停止,那放在粒度最小的位置去)
				@unlink($lockFile);
				break;
			}

			$appUrl = trim($app['app_url']);
			if(200 != getHttpStatus($appUrl)) {//这里只检测200状态
				continue;
			}

			$appName = $app['app_id'] . '_' . md5($appUrl);
			$saveFile = $this->_genDownPath() . $appName;
			$downResult = false;
			$downSize = 0;
			//用来保存下载资源的各片段文件名
			$saveFileSplit = array();
			$saveCount = 0;
			do {
				$fileSplitName = empty($saveCount) ?
								$saveFile :
								$saveFile . '_' . $saveCount . '_' . $downSize;
				$saveFileSplit[$saveCount++] = $fileSplitName;
				$downResult = downUrl($appUrl, $fileSplitName, $downSize);
				if(file_exists($fileSplitName)) {
					$downSize += filesize($fileSplitName);
				}
			} while(!$downResult);

			$this->_mergeDown($saveFileSplit, true);
			$md5 = md5_file($saveFile);
			@unlink($saveFile);

			$mMd5	->where(array('app_id'	=> intval($app['app_id']),))
					->save(array('app_md5'	=> $md5,));
			file_put_contents('updateAppMd5.txt', $mMd5->_sql() . PHP_EOL . ';', FILE_APPEND);
			$this->_writeMd5File($idx,
						$app['app_id'],
						$md5,
						$appUrl);
		}
	}

	/**
	 * 网络原因造成下载不完整,分段下载,此方法进行合并
	 *
	 * @param array fileParts 第一个为实际要保存的文件名,后续的每个按顺序合并到第一个
	 */
	private function _mergeDown($fileParts, $rmTrash = false) {
		if(count($fileParts) > 1) {
			$saveFile = array_shift($fileParts);
			foreach($fileParts as $filePart) {
				mergeFile($saveFile, $filePart);
				$rmTrash && @unlink($filePart);
			}
		}
	}

	private function _writeMd5File($idx, $appid, $md5, $appurl) {
		$appMd5 = array(
			'app_id'	=> intval($appid),
			'app_md5'	=> $md5,
			'app_url'	=> $appurl,
		);
		file_put_contents($this->md5FileToWrite,
						"{$appid}  {$md5}  {$appurl}" . "\r\n",
						FILE_APPEND);
		file_put_contents($this->md5FileToWrite . '.php',
						$idx . '=>' . var_export($appMd5, true) . ",\r\n",
						FILE_APPEND);
	}

	private function _genDownPath() {
		$date_format = date('YmdH', $_SERVER['REQUEST_TIME']);
		$year = substr($date_format, 0, 4) . '/';
		$month = substr($date_format, 4, 2) . '/';
		$day = substr($date_format, 6, 2) . '/';
		$hour = substr($date_format, 8) . '/';
		// $dir = BASE_PATH . $this->getImgPath($img_type) . $year . $month . $day . $hour;
		$dir = BASE_PATH . './downapp/' . $year . $month;
		@mk_dir($dir);
		return $dir;
	}//end _genUpImgPath()

	private function _updateHash($app_md5, $app_store) {
		$app_md5	= trim($app_md5);
		$app_store	= trim($app_store);
		$mApp = M('App');
		$findAppId = $mApp	->where("app_md5 = '' AND app_store = '{$app_store}'")
							->getField($mApp->getPk());
		if($findAppId) {
			$mApp->save(array(
				$mApp->getPk()	=> intval($findAppId),
				'app_md5'		=> $app_md5,
			));
		}
	}

	private function _finish_request($time = 1000) {
		if(function_exists('fastcgi_finish_request')) {//如果服务跑的是 fastcgi 模式,就交给服务器自己去跑吧
			fastcgi_finish_request();
		}
		set_time_limit($time);
	}

}
?>

-------------------------------------------------------------------------------------------------------------------------------------------------------------------


啊!不太好意思,关于进程锁机制,这个程序不是我那个机制,因为当时只是想能让任务停止,所以只是检测到有锁时就终止任务,没有做到保证任务唯一性。也就是说只实现了一半的机制呵呵。。。。完整的机制其实就是反过来的:在程序开始时检测如果有锁就不执行任务,已经开始的任务先touch出锁文件来,然后每个循环开始时检测锁还在不在,不在就终止。一直到任务结束时再把锁文件删除掉。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值