Php áëîêèðîâêà

Çàêàæèòå õîðîøèé ñâàðî÷íûé àïïàðàò Ñâàðîã PRO äëÿ êîìôîðòíîé è ë¸ãêîé ñâàðêè.

Php-web-äèçàéí > Php áëîêèðîâêà

Ñóòü ïðîáëåìû òàêîâà:
Åñòü áàçà äàííûõ, èñïîëüçóåìàÿ íà ñàéòå (íàïðèìåð, áàçà äëÿ ðåãèñòðàöèè ïîëüçîâàòåëåé, êóäà çàïèñûâàåòñÿ èõ èìÿ è email), îíà ëåæèò â òåêñòîâîì ôàéëå ïîñòðî÷íî (â äàëüíåéøåì, "file_base.dat".). Äâà ïîëüçîâàòåëÿ àêòèâèçèðóþò ñåðâåð ÷åðåç êîìàíäíóþ ñòðîêó â áðîóçåðå, äëÿ ââîäà ñâîõ èìåí è email. Ñåðâåð îòñûëàåò èõ ê ñêðèïòó. Îáà ïîëüçîâàòåëÿ "íà÷èíàþò äâèæåíèå" ïî ñêðèïòîâîìó ïîòîêó (òåêñòó php ôàéëà) ñâåðõó âíèç, ïðè÷åì, Ïåðâûé "áåæèò" íà äîëþ ñåêóíäû áûñòðåå Âòîðîãî. Êîãäà îíè äîñòèãàþò òîãî ìåñòà, ãäå ñêðèïò èñïîëíÿåò èõ çàïðîñ, äâèæåíèå ïî ïîòîêó îñòàíàâëèâàåòñÿ, â èõ áðîóçåð âûâîäèòñÿ ñãåíåðèðîâàííàÿ ñêðèïòîì ñòðàíèöà â âèäå html. ×òîáû èç file_base.dat ïðî÷èòàòü äàííûå, ýòîò ôàéë íàäî îòêðûòü íà ÷òåíèå (ôóíêöèÿ - @file), ÷òîáû çàïèñàòü ÷òî-òî â íåãî, íàäî îòêðûòü íà çàïèñü (ôóíêöèÿ - @fopen).  ñêðèïòå ýòî âûãëÿäèò òàê:

<?
……
// ÷èòàåì äàííûå èç ôàéëà-áàçû
$f = @file ("file_base.dat", "r"); // çäåñü íàõîäèòñÿ 2 ïîëüçîâàòåëü
// çäåñü èäåò òåêñò ñêðèïòà
// îòêðûâàåì ôàéë-áàçó íà çàïèñü
$fp = @fopen ("file_base.dat", "w"); // çäåñü íàõîäèòñÿ 1 ïîëüçîâàòåëü
// çàïèñûâàåì â ôàéë-áàçó äàííûå èç âûøå
// ïðî÷èòàííîãî file_base.dat - ïåðåìåííàÿ $f
// è äîáàâëÿåì åùå îäíó ñòðîêó ñ äàííûìè íîâîãî ïîëüçîâàòåëÿ
// çàêðûâàåì ôàéë-áàçó
@fclose ($fp);
……
?>

Òî åñòü, ÷òîáû íîâûé ïîëüçîâàòåëü ïîÿâèëñÿ â íàøåé file_base.dat, ìû ñ÷èòûâàåì îòòóäà èíôîðìàöèþ, êîòîðóþ êëàäåì â ïåðåìåííóþ $f, çàòåì çàïèñûâàåì â ýòîò æå ôàéë ýòó ïåðåìåííóþ $f (ïðè ýòîì, file_base.dat ïåðåïèñûâàåì ïîëíîñòüþ) è âíèçó äîïèñûâàåì åùå îäíó ñòðîêó ñ äàííûìè íàøåãî íîâîãî ïîëüçîâàòåëÿ. Ìû âèäèì, ÷òî íà ÷òåíèå è íà çàïèñü file_base.dat îòêðûâàåòñÿ â ðàçíûõ ìåñòàõ íàøåãî ñêðèïòà. Åñëè îáà ïîëüçîâàòåëÿ, îäíîâðåìåííî, äîñòèãëè "ñâîåãî" ìåñòà â ýòîì ñêðèïòå, íî îäèí èç file_base.dat òîëüêî íà÷àë ÷èòàòü äàííûå, à äðóãîé óæå èõ ïðî÷èòàë, ïðîäâèíóëñÿ ÷óòü íèæå è, â ýòîò ìîìåíò, íàõîäèòñÿ íà îòìåòêå çàïèñè â ôàéë file_base.dat ñâîèõ äàííûõ. Òî, â ýòîì ñëó÷àå, îò íàøåãî ôàéëà file_base.dat íè÷åãî íå îñòàíåòñÿ.  ñâÿçè ñ ýòèì, â Php áûëà ââåäåíà ôóíêöèÿ ñîâìåñòíîãî äîñòóïà @flock, êîòîðàÿ ïðèçâàíà íå äîïóñêàòü ñîâìåñòíûé äîñòóï ê ôàéëó íà ÷òåíèå è çàïèñü.

<?
……
// ÷èòàåì äàííûå èç ôàéëà ôàéëà-áàçû
$f = @file ("file_base.dat", "r"); // çäåñü íàõîäèòñÿ 2 ïîëüçîâàòåëü
// çäåñü èäåò òåêñò ñêðèïòà
// îòêðûâàåì ôàéë-áàçó íà çàïèñü
$fp = @fopen ("file_base.dat", "w"); // çäåñü íàõîäèòñÿ 1 ïîëüçîâàòåëü
// áëîêèðóåì ôàéë-áàçó íà ÷òåíèå
@flock ($fp, LOCK_EX)
// çàïèñûâàåì â ôàéë-áàçó äàííûå èç âûøå
// ïðî÷èòàííîãî file_base.dat - ïåðåìåííàÿ $f
// è äîáàâëÿåì åùå îäíó ñòðîêó ñ äàííûìè íîâîãî ïîëüçîâàòåëÿ
// ñíèìàåì áëîêèðîâêó
@flock ($fp, LOCK_UN)
// çàêðûâàåì ôàéë-áàçó
@fclose ($fp);
……
?>

Òåïåðü, âðîäå áû âñå íîðìàëüíî, ôàéë áëîêèðóåòñÿ íà ÷òåíèå, êîãäà â íåãî ÷òî-òî ïèøóò. Íî, ÷òî ïðîèçîéäåò â íàøåì ñëó÷àå, êîãäà, 1 ïîëüçîâàòåëü ïðî÷èòàë äàííûå ôàéëà è ïðèøåë ê îòìåòêå îòêðûòèÿ file_base.dat íà çàïèñü, îðêûë åãî è çàáëîêèðîâàë íà ÷òåíèå ôóíêöèåé @flock, à 2 ïîëüçîâàòåëü, èìåííî â ýòîò ìîìåíò ïðèøåë ê òî÷êå, ãäå äàííûå èç ôàéëà ñ÷èòûâàþòñÿ ôóíêöèåé @file? 2 ïîëüçîâàòåëü íè÷åãî íå ïðî÷èòàåò èç ýòîãî ôàéëà, ïîòîìó ÷òî file_base.dat áëîêèðîâàí 1 ïîëüçîâàòåëåì êîìàíäîé @flock íà ÷òåíèå. 2 ïîëüçîâàòåëü ïðîñòî "ïðîáåæèò" äàëüøå ïî ïîòîêó ñêðèïòà íè÷åãî èç ôàéëà file_base.dat íå ïðî÷èòàâ, òî åñòü íàøà ïåðåìåííàÿ $f áóäåò ïóñòîé. Íî, äàëüøå-òî îí áóäåò çàïèñûâàòü â ýòîò ôàéë, òî ÷òî ðàíåå ñ íåãî ñ÷èòàë.  èòîãå, â íàø file_base.dat çàïèøåòñÿ òîëüêî îäíà ñòðîêà ñ åãî èìåíåì è email. Òî åñòü, file_base.dat áóäåò ïîòåðÿí.
 ñâÿçè ñ ÷åì, áûëà ïðèäóìàíà ôóíêöèÿ: read_file

function read_file($path)
{
if(!is_file($path)) {return false; }
elseif(!filesize($path)) {return array(); }
elseif($array=file($path)) {return $array; }
else { while(!$array=file($path)){sleep(1);} return $array; }
}

Ñóòü åå òàêîâà: ïîêà ôàéë áëîêèðîâàí íà ÷òåíèå, ïîëüçîâàòåëü, êîòîðûé õî÷åò ñ÷èòàòü èç íåãî èíôîðìàöèþ, íàõîäèòñÿ â öèêëå, òî åñòü, êàê áû "ñòîèò" íà ìåñòå, íå "áåæèò" äàëüøå ïî òåêñòó ñêðèïòà, îæèäàÿ, êîãäà ôàéë ðàçáëîêèðóåòñÿ íà ÷òåíèå.  èòîãå íàø ñêðèïò ïðèíèìàåò òàêîé âèä:

<?
function read_file($path)
{
if(!is_file($path)) {return false; }
elseif(!filesize($path)) {return array(); }
elseif($array=file($path)) {return $array; }
else { while(!$array=file($path)){sleep(1);} return $array; }
}
……
// ïðîâåðÿåì çàáëîêèðîâàí ëè ôàéë íà ÷òåíèå,
// åñëè çàáëîêèðîâàí, íàçíà÷àåì öèêë ñ îñòàíîâêîé,
// ïîêà áëîêèðîâêà íå áóäåò ñíÿòà, ïîñëå ñíÿòèÿ áëîêèðîâêè,
// ÷èòàåì äàííûå èç ôàéëà ôàéëà-áàçû
$f = read_file ("file_base.dat", "r"); // çäåñü íàõîäèòñÿ 2 ïîëüçîâàòåëü
// çäåñü èäåò òåêñò ñêðèïòà
// îòêðûâàåì ôàéë-áàçó íà çàïèñü
$fp = @fopen ("file_base.dat", "w"); // çäåñü íàõîäèòñÿ 1 ïîëüçîâàòåëü
// áëîêèðóåì ôàéë-áàçó íà ÷òåíèå
@flock ($fp, LOCK_EX)
// çàïèñûâàåì â ôàéë-áàçó äàííûå èç âûøå
// ïðî÷èòàííîãî file_base.dat - ïåðåìåííàÿ $f
// è äîáàâëÿåì åùå îäíó ñòðîêó ñ äàííûìè íîâîãî ïîëüçîâàòåëÿ
// ñíèìàåì áëîêèðîâêó
@flock ($fp, LOCK_UN)
// çàêðûâàåì ôàéë-áàçó
@fclose ($fp);
……
?>

Âðîäå áû âñå íîðìàëüíî. Êîãäà 1 ïîëüçîâàòåëü íà÷àë çàïèñûâàòü ñâîè äàííûå â file_base.dat, 2 "ñïèò" îäíó ñåêóíäó, îæèäàÿ ðàçáëîêèðîâêè file_base.dat, êîãäà file_base.dat ðàçáëîêèðîâàí, îí ñ÷èòûâàåò èç íåãî èíôîðìàöèþ è íà÷èíàåò äâèæåíèå äàëüøå. Íî, åñòü îäíî "íî". Åñëè 2 ïîëüçîâàòåëü ÷óòü áûñòðåå 1 ïîëüçîâàòåëÿ "äîáåæàë" äî "ñâîåãî" ìåñòà, ïðî÷èòàë ïîëîâèíó äàííûõ èç íàøåãî file_base.dat, êîòîðûé åùå íå áûë áëîêèðîâàí, è, èìåííî, â ýòîò ìîìåíò, 1 "äîáåæàë" ïî òåêñòó ñêðèïòà äî @flock ($fp, LOCK_EX), òî åñòü çàáëîêèðîâàë íàø ôàéë, òî ïåðåìåííàÿ $fp áóäåò èìåòü òîëüêî ïîëîâèíó äàííûõ èç íàøåãî file_base.dat, ïîòîìó ÷òî, èìåííî, â ýòîò ìîìåíò file_base.dat áûë çàáëîêèðîâàí.  èòîãå, â íàø file_base.dat áóäåò çàïèñàíà ïîëîâèíà èíôîðìàöèè èç íàøåé áàçû + îäíà ñòîðêà íîâîãî ïîëüçîâàòåëÿ. Òî åñòü, îïÿòü, file_base.dat ìû ïîòåðÿëè.

Ñóòü ïîäõîäà, êîòîðûé ïðåäëàãàåì ìû äëÿ ðåøåíèÿ ýòîé ïðîáëåìû òàêîé:
Âî âðåìÿ ñ÷èòûâàíèÿ èç ôàéëà èëè çàïèñè â íåãî äàííûõ, íà ñàéòå ïîÿâëÿåòñÿ òåêñòîâàÿ ñòðîêà ñ íàçâàíèåì ýòîãî ôàéëà, êîãäà èç ôàéëà èíôîðìàöèÿ ïðî÷èòàëàñü èëè çàïèñàëàñü, ýòà òåêñòîâàÿ ñòðîêà óäàëÿåòñÿ. Ïîêà îíà åñòü, çíà÷èò êòî-òî óæå èñïîëüçóåò áàçó äàííûõ íà ÷òåíèå èëè çàïèñü, ïîëüçîâàòåëü, êîòîðûé õî÷åò îáðàòèòüñÿ ê ýòîé áàçå íà ÷òåíèå èëè çàïèñü, íàõîäèòñÿ â öèêëå, îæèäàÿ ïîêà òåêñòîâàÿ ñòðîêà èñ÷åçíåò. Êàê òîëüêî îíà èñ÷åçàåò, îí ÷èòàåò èëè ïèøåò â áàçó äàííûõ, ñàì ïðè ýòîì ñîçäàâàÿ òàêóþ æå òåêñòîâóþ ñòðîêó, òåì ñàìûì ïðåïÿòñòâóÿ äîñòóïó äâóõ è áîëåå ïîëüçîâàòåëåé ê áàçå. 

Äëÿ ÷åãî, íà ñàéòå ñîçäàäèì ïàïêó äëÿ õðàíåíèÿ òåêñòîâûõ ñòðîê. Íàïðèìåð, lock. Íàø ôàéë íà ñàéòå ëåæèò â ïàïêå basedir, çíà÷èò â ïàïêå lock íàäî ñîçäàòü ïàïêó basedir. Òåïåðü, êîãäà ïîëüçîâàòåëü îáðàùàåòñÿ ê file_base.dat, äëÿ ÷òåíèÿ èëè çàïèñè â íåãî, àáñîëþòíûé ïóòü https://íàø_ñàéò.ru/basedir/file_base.dat, â ïàïêå https://íàø_ñàéò.ru/lock/basedir/ ïîÿâëÿåòñÿ ôàéë-ñòðîêà file_base.dat.tmp, àáñîëþòíûé ïóòü - https://íàø_ñàéò.ru/lock/basedir/file_base.dat.tmp, çàêðûâàÿ äîñòóï ê file_base.dat, êàê òîëüêî ïîëüçîâàòåëü ñ÷èòàë èëè çàïèñàë èíôîðìàöèþ â íàø ôàéë-áàçó, òåêñòîâàÿ ñòðîêà  file_base.dat.tmp óäàëÿåòñÿ, îòêðûâàÿ äîñòóï äðóãèì ïîëüçîâàòåëÿì ê file_base.dat.

<?
// îáúÿâëÿåì äèðåêòîðèþ äëÿ âðåìåííûõ ôàéëîâ
$Lock_dir = "lock";
// ôóíêöèÿ äëÿ ñîçäàíèÿ âðåìåííûõ òåêñòîâûõ ôàéëîâ
function touchString($file) {
global $Lock_dir;
$tmp = "$Lock_dir/".$file.".tmp";
while(1) {
if (is_file($tmp))
{
while(file_exists($tmp))
{
$file_exist++;
if($file_exist > 10){break;}
clearstatcache();
sleep(1);
}
}
return touch($tmp);
}
}
// ôóíêöèÿ óäàëÿþùàÿ âðåìåííûå òåêñòîâûå ôàéëû
function delString($file) {
global $Lock_dir;
$tmp = "$Lock_dir/".$file.".tmp";
return unlink($tmp);
}
// àëüòåðíàòèâà ôóíêöè @file
function FileArray($file) {
if (!is_readable($file)) return FALSE;
touchString($file);
$bufer = file($file);
delString($file);
return $bufer;
}
// àëüòåðíàòèâà ôóíêöè @fopen
function OpenFile($file, $mode) {
touchString($file);
return fopen($file, $mode);
}
// àëüòåðíàòèâà ôóíêöè @fclose
function CloseFile($fido, $file) {
$sito = fclose($fido);
delString($file);
return $sito;
}
……
// ïðîâåðÿåì åñòü ëè âðåìåííûé ôàéë, çàïðåùàþùèé
// ÷òåíèå è çàïèñü â ôàéë-áàçó, åñëè îí åñòü, îñòàíàâëèâàåì
// ïîëüçîâàòåëÿ â öèêëå, ãäå îí îæèäàåò ïîêà âðåìåííûé ôàéë
// èñ÷åçíåò, êàê òîëêî îí èñ÷åçàåò, ñàìè ñîçäàåì òàêîé æå âðåìåííûé
// ôàéë, çàïðåùàÿ äîñòóï ê ôàéëó-áàçå, òîëüêî ïîñëå ýòîãî ÷èòàåì äàííûå èç
// ôàéëà-áàçû, óäàëÿåì âðåìåííûé ôàéë, îòêðûâàÿ äîñòóï ê áàçå
// äðóãèì ïîëüçîâàòåëÿì
$f = FileArray ("basedir/file_base.dat"); // çäåñü íàõîäèòñÿ 2 ïîëüçîâàòåëü
// çäåñü èäåò òåêñò ñêðèïòà
// ïðîâåðÿåì åñòü ëè âðåìåííûé ôàéë, çàïðåùàþùèé
// ÷òåíèå è çàïèñü â ôàéë-áàçó, åñëè îí åñòü, îñòàíàâëèâàåì
// ïîëüçîâàòåëÿ â öèêëå, ãäå îí îæèäàåò ïîêà âðåìåííûé ôàéë
// èñ÷åçíåò, êàê òîëêî îí èñ÷åçàåò, ñàìè ñîçäàåì òàêîé æå âðåìåííûé
// ôàéë, çàïðåùàÿ äîñòóï ê ôàéëó-áàçå, òîëüêî ïîñëå ýòîãî îòêðûâàåì
// ôàéë-áàçó íà çàïèñü, âðåìåííûé ôàéë íå óäàëÿåòñÿ, çàêðûâàÿ äîñòóï ê áàçå
// äðóãèì ïîëüçîâàòåëÿì
$fp = OpenFile ("basedir/file_base.dat", "w"); // çäåñü íàõîäèòñÿ 1 ïîëüçîâàòåëü
// çàïèñûâàåì â ôàéë-áàçó äàííûå èç âûøå
// ïðî÷èòàííîãî basedir/file_base.dat - ïåðåìåííàÿ $f
// è äîáàâëÿåì åùå îäíó ñòðîêó ñ äàííûìè íîâîãî ïîëüçîâàòåëÿ
// çàêðûâàåì ôàéë-áàçó, óäàëÿåì âðåìåííûé ôàéë, îòêðûâàÿ äîñòóï ê áàçå
// äðóãèì ïîëüçîâàòåëÿì
CloseFile ($fp, "basedir/file_base.dat");
// îáðàòèòå âíèìàíèå, ÷òî â CloseFile äâà àðãóìåíòà: $fp è
// basedir/file_base.dat
……
?>

 ýòîé ñèòóàöèè ìû âèäèì, ÷òî êîãäà îäèí ïîëüçîâàòåëü äîñòèã òî÷êè ñ÷èòûâàíèÿ ñ file_base.dat èëè, íàîáîðîò, äðóãîé ïûòàåòñÿ çàïèñàòü â file_base.dat èíôîðìàöèþ, íàì íè÷åãî íå ñòðàøíî. Ïîòîìó ÷òî, êàê â îäíîì, òàê è â äðóãîì ñëó÷àå, ïîÿâèëñÿ ìàëåíüêèé òåêñòîâûé ôàéë file_base.dat.tmp, êîòîðûé íå äàåò íè îäíîìó íè äðóãîìó ñîâìåñòíî ÷èòàòü èëè ïèñàòü â file_base.dat.

 íàøåì âàðèàíòå åñòü îäíà "äûðà", åñëè âäðóã ïîëüçîâàòåëü îáðàòèëñÿ ê áàçå äàííûõ, ñîçäàâ òåêñòîâóþ ñòðîêó áëîêèðîâêè, íåçàêîí÷èë êàê-áû öèêë (ñâåò ïîãàñ è êîìïüþòåð âûêëþ÷èëñÿ), è ýòà òåêñòîâàÿ áëîêèðóþùàÿ ñòðîêà îñòàëàñü ëåæàòü â ïàïêå Lock, òî íàøà ïðîãðàììà "ïîäâèñíåò", íå äàâàÿ íèêîìó ïðîéòè, èç-çà ýòîãî îñòàâøåãîñÿ ôëàãà. Ìû íå ñòàëè óñëîæíÿòü ôóíêöèè ïðîâåðêîé íà óäàëåíèå ýòîãî ôàéëà-ôëàãà, à, ïðîñòî ââåëè â ôóíêöèè åãî ñîçäàþùåé, ïðåäåë "ïîäâèñàíèÿ" êîìïüþòåðà 10 ñåêóíä ($file_exist++; if($file_exist > 10){break;}), ÷åðåç 10 ñåêóíä îí àâòîìàòè÷åñêè âûéäåò èç öèêëà è ñîòðåò âðåìåííûé ôàéë.  ýòîì êðîåòñÿ îïàñíîñòü, íî îíà íè÷òîæíà ìàëà ïî ñðàâíåíèþ ñ òåìè, êîòîðûå áûëè îïèñàíû âûøå. Ñ÷èòàåì, ÷òî ïîäõîä îïèñàííûé âûøå, çàùèòèò îò îáâàëà áàçû äàííûõ, êîòîðàÿ ëåæèò â òåêñòîâîì ôàéëå è íå áóäåò îñîáî çàìåòíî ôëàãîâ áëîêèðîâêè, êîãäà íà ñàéòå åå, îäíîâðåìåííî èñïîëüçóåò äî 10 ÷åëîâåê. Ìû çíàåì, ÷òî íàø ñêðèïò ñåðâåðîì èñïîëíÿåòñÿ 0,5 ñåê, â ýòîì ñëó÷àå åãî ïðîïóñêíàÿ ñïîñîáíîñòü â ÷àñ ñîñòàâèò äî 1800 ÷åëîâåê. Óìåíüøèì åå âäâîå, 900 ÷åëîâåê, çà ñóòêè 21600 ÷åëîâåê. Ñîãëàñèòåñü, è áåç MySql ìîæíî îáîéòèñü.

Äëÿ ïðîâåðêè ðàáîòîñïîñîáíîñòè ýòèõ ôóíêöèé çàïèøèòå âðó÷íóþ â ïàïêó Lock ýòîò ñàìûé áëîêèðóþùèé ôàéë file_base.dat.tmp, â íàøåì ñëó÷àå, ýòî: https://íàø_ñàéò.ru/lock/basedir/file_base.dat.tmp, òåì ñàìûì, áëîêèðóÿ äîñòóï ê áàçå https://íàø_ñàéò.ru/basedir/file_base.dat, çàïóñòèòå ïðîãðàììó, îáðàòèòåñü ê áàçå, âû óâèäèòå, ÷òî ôëàã ðàáîòàåò è áðîóçåð "ñòîèò" íà ìåñòå, óäàëèòå file_base.dat.tmp ôàéë (ðàçáëîêèðóÿ) è ïîäâèñàíèå çàêîí÷èòñÿ.

Òåïåðü ó íàñ åñòü ôóíêöèè:
1. FileArray , çàìåíÿþùàÿ @file
2. OpenFile , çàìåíÿþùàÿ @fopen
3. CloseFile , çàìåíÿþùàÿ @fclose



Àëåêñàíäð Óñòþãîâ
https://omsk-777.ru/



Ïðè ïåðåïå÷àòêå èíôîðìàöèè äåëàéòå, ïîæàëóéñòà, ññûëêó íà íàø ñàéò (ëèáî ïîäåëèòüñÿ âêîíòàêòå, ãóãë+ èëè äåëèòåñü â îäíîêëàññíèêàõ ñòàòü¸é). Ñïàñèáî!

Èñòî÷íèê: www.wr-script.ru©, 2004-2026ã.

Äåëèòåñü ñ äðóçüÿìè â ñîöñåòÿõ. Àêòèâíûå êîììåíòàòîðû ïîëó÷àþò ñâåæèå ñêðèïòû è áåñïëàòíûå äîðàáîòêè îò WR-Script.ru!

Âåðíóòüñÿ ê ñòàòüÿì ïîìîùè WEB-ìàñòåðó

WR-Счётчик