PHP: Ko'chirish tezligini chegaralash.

Salom. Anchadan beri ayrim sabablarga ko'ra maqola ham yoza olmadim. Bugun o'ylab-o'ylab nima haqida yozishni topdim. Manimcha bu narsa ko'pchilikka ham zarur bo'ladi. Hozirgi kunda Tas-Ix ham rivojlanib bormoqda va ko'pchilik yangi sayt ochayotganlar saytimdan nimalarni ko'chirishdi va har bir ko'chiruvchiga cheklangan ravishda tezlik berish haqida o'ylasa kerak.

Keling bugun shu muammolarni PHP da hal qilib bersam.

Ko'pchilik hollarda ko'chirishga bergan saytlar faylga to'g'ridan-to'g'ri ssilka (link) berib qo'yishadi. To'g'ri bunda PHP ning hech qanday ishtiroki shartmas. Lekin buni ayrim minuslari bor:

— Qaysi fayllarni ko'p ko'chirishdi
— Tezlikda chegaralab bo'lmaydi
— Faylni xohlagan odam xohlaganicha ko'chirish mumkin

va shunga o'xshash bir qancha sabablarni sanasa bo'ladi. Kelin oldin fayllarni yashirib ularni PHP skript orqali ko'chirishni ko'rsak.

Buning uchun WEB saytimiz yonida yoki PHP ga ruxsat berilgan biror bir papkaga fayllarni qo'yamiz. Misol uchun files nomli papka bo'lsin. Va shu papka ichiga .htacces fayl yaratamiz va fayllarga ruxsatni yopamiz. Quyidagicha
deny from all


endi quyidagicha download.php fayl yaratamiz:
<?php

//FILE papkasi manzilini olish
$files_dir = dirname(__FILE__).DIRECTORY_SEPARATOR."files".DIRECTORY_SEPARATOR;

//$_GET ni tekshirish
if ( ! isset($_GET['file']))
	show_404();

//To'liq fayl manzilini olish
$filename = $_GET['file'];
$filepath = realpath($files_dir.$filename);

//Yozilgan fayl /files papkaning ichidaligini tekshirish
if (!$filepath || strncmp($filepath, $files_dir, strlen($files_dir)) !== 0)
	show_404();

//shu faylning mimetype ni olish. Buning uchun
//php-fileinfo extenstion kerak bo'ladi
$mimetype = get_mimetype($filepath);

//HTTP headers. Ya'ni browserga fayl ko'chirilaypti
//degan xabarni jo'natish
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$filename");
header("Content-Type: $mimetype");
header('Content-Length: ' . filesize($filepath));

//faylni o'qish va uzatish
readfile($filepath);

//xatolikni chiqaradigan funksiya
function show_404()
{
	header("HTTP/1.1 404 Not found");
	die("Fayl topilmadi");
}

//fayl mimetype ni oladigan funksiya
function get_mimetype($filename)
{
	$finfo = finfo_open(FILEINFO_MIME); 
	$mimetype = finfo_file($finfo, $filename); 
	finfo_close($finfo);

	return $mimetype;
}

Agar browserni ochib site/download.php?file=rasm.jpg deb tersangiz va rasm.jpg fayli «files» papkasida bo'lsa sizga xuddi fayl ko'chirayotganday oyna chiqadi. Endi siz site/files/rasm.jpg deb yozsangiz
Forbidden

You don't have permission to access /files/rasm.jpg on this server.

xatoni olasiz.

Demak sayt foydalanuvchilar fayllarni to'g'ridan-to'g'ri ko'chirolmaydi. Endi keling 2-muammomiz, qaysi fayl necha marta ko'chirildi degan hisoblagichni qo'shsak, bu juda oson. download.php dagi readfile funksiyasidan keyin yozamiz
...
//faylni o'qish va uzatish
readfile($filepath);

//shu yerga hisob
//mysql_query("UPDATE files SET downloaded = downloaded + 1 WHERE id = $fid");

ko'rinishida.

Yozilgan tepadagi PHP skriptda ko'chirishga chegara berilmagan. Endi keling shu faylni ko'chirish tezligiga chegara qo'ysak, ya'ni 1 mbps tezlik ko'chirib olishi mumkin bo'lgan odamga, biz PHP yordamida 1 kbps tezlikda ko'chiradigan qilamiz. Ya'ni internet tezligi qanaqa bo'lishiga qaramasdan biz bergan tezlikda ko'chiradigan qilamiz.

Buning uchun man quyidagicha funksiyani yozdim
//faylni o'qib tezlikni chegaralash
function read_file($filename, $speed)
{
	//browserga ma'lumotlarni ko'natish
	flush();
	
	$read_size = round(1024 * $speed);
	$file = fopen($filename, "rb");
	
	while( ! feof($file))
	{
		//dastlabki vaqtni olish
		$start = microtime(true);
		
		//fayldan o'qish
		echo fread($file, $read_size);
	
		//browserga ma'lumotlarni ko'natish
		flush();
		
		//fread va flush ni ketgan vaqtini 1 dan ayirish
		$diff = 1 - (microtime(true) - $start);
		
		//$diff vaqtcha kutish
		if ($diff > 0)
			usleep(1000000 * $diff);
	}
}

va
//faylni o'qish va uzatish
read_file($filepath, 10);

readfile ni o'zgartirib qo'yamiz. Funksiyadagi ikkinchi parametr 10 kb/s deganni anglatadi. Agar funksiyaga e'tibor bergan bo'lsangiz, microtime ishlatilgan. Nimaga?

Agar kliyentga 10 kb/s tezlikda jo'natish kerak bo'lsa, fayldan 10 kb ni o'qib, 1 sekund kutib turish kerak bo'ladi. Lekin fread bilan flush lar ham qanchadir vaqtni oladi. Shuning hisobidan sleep(1) qilib ishlatilsa tezlik 9.9 kbps tezlikda bo'lishi mumkin. Umuman olganda tezlikni 100% aniqlikda uzatishga erishish uchun qilingan. Lekin bu ham 99% to'g'ri ishlaydi.

Ha yana qo'shimcha set_time_limit(0); qo'shilgan. Chunki tezlik chegaralangandan keyin PHP fayl 30 sekunddan ko'p ishlashi mumkin. Shunaqa «timeout» bermasligi uchun shu funksiya ishlatiladi.

Shunaqa qilib oxirgi download.php faylimiz quyidagicha.
<?php

//PHP ni har doim ishlab turadigan qilish
set_time_limit(0);

//FILE papkasi manzilini olish
$files_dir = dirname(__FILE__).DIRECTORY_SEPARATOR."files".DIRECTORY_SEPARATOR;

//$_GET ni tekshirish
if ( ! isset($_GET['file']))
	show_404();

//To'liq fayl manzilini olish
$filename = $_GET['file'];
$filepath = realpath($files_dir.$filename);

//Yozilgan fayl /files papkaning ichidaligini tekshirish
if (!$filepath || strncmp($filepath, $files_dir, strlen($files_dir)) !== 0)
	show_404();

//shu faylning mimetype ni olish. Buning uchun
//php-fileinfo extenstion kerak bo'ladi
$mimetype = get_mimetype($filepath);

//HTTP headers. Ya'ni browserga fayl ko'chirilaypti
//degan xabarni jo'natish
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=$filename");
header("Content-Type: $mimetype");
header('Content-Length: ' . filesize($filepath));

//faylni o'qish va uzatish
read_file($filepath, 10);

//shu yerga hisob
//mysql_query("UPDATE files SET downloaded = downloaded + 1 WHERE id = $fid");

//faylni o'qib tezlikni chegaralash
function read_file($filename, $speed)
{
	//browserga ma'lumotlarni ko'natish
	flush();
	
	$read_size = round(1024 * $speed);
	$file = fopen($filename, "rb");
	
	while( ! feof($file))
	{
		//dastlabki vaqtni olish
		$start = microtime(true);
		
		//fayldan o'qish
		echo fread($file, $read_size);
	
		//browserga ma'lumotlarni ko'natish
		flush();
		
		//fread va flush ni ketgan vaqtini 1 dan ayirish
		$diff = 1 - (microtime(true) - $start);
		
		//$diff vaqtcha kutish
		if ($diff > 0)
			usleep(1000000 * $diff);
	}
}

//xatolikni chiqaradigan funksiya
function show_404()
{
	header("HTTP/1.1 404 Not found");
	die("Fayl topilmadi");
}

//fayl mimetype ni oladigan funksiya
function get_mimetype($filename)
{
	$finfo = finfo_open(FILEINFO_MIME); 
	$mimetype = finfo_file($finfo, $filename); 
	finfo_close($finfo);

	return $mimetype;
}


Bu holatning asosiy minuslaridan biri, resursni juda ko'p yeyib qo'yishi mumkin.

Savollar bo'lsa marhamat.

2 комментария

Sunna8
Agar bitta odam shu faylni 2 yoki undan ko`p martta yuklasachi?.. Sessiya yoki cookielar orqali faqat bir martta ko`chira oladigan qilish kerak
0
shranet
U holatda read_file funksiyasini chaqirishdan oldin, sessiyaga yoki IP ga block qo'yasiz bazadan. read_file bajarilib bo'lgandan keyin shu blockni ochasiz. Tepada esa block yoki block holatida emasligini tekshirasiz.

Javob kechikkani uzrli sabablarga ko'ra :)
0