Ito ang command perlipc na maaaring patakbuhin sa OnWorks na libreng hosting provider gamit ang isa sa aming maramihang libreng online na workstation gaya ng Ubuntu Online, Fedora Online, Windows online emulator o MAC OS online emulator
PROGRAMA:
NAME
perlipc - Perl interprocess na komunikasyon (mga signal, fifos, pipe, ligtas na subprocess,
socket, at semaphore)
DESCRIPTION
Ang mga pangunahing pasilidad ng IPC ng Perl ay binuo mula sa magagandang lumang Unix signal, pinangalanang pipe,
nagbubukas ng pipe, ang mga gawain ng socket ng Berkeley, at mga tawag sa SysV IPC. Ang bawat isa ay ginagamit sa bahagyang
iba't ibang sitwasyon.
Signal
Gumagamit ang Perl ng isang simpleng modelo ng paghawak ng signal: ang %SIG hash ay naglalaman ng mga pangalan o reference ng
mga tagapangasiwa ng signal na naka-install ng gumagamit. Ang mga humahawak na ito ay tatawagin na may isang argumento which is
ang pangalan ng signal na nag-trigger nito. Ang isang senyas ay maaaring sadyang makabuo mula sa a
partikular na pagkakasunud-sunod ng keyboard tulad ng control-C o control-Z, na ipinadala sa iyo mula sa isa pa
proseso, o awtomatikong na-trigger ng kernel kapag nagkaroon ng mga espesyal na kaganapan, tulad ng a
paglabas ng proseso ng bata, ang sarili mong proseso ay nauubusan ng stack space, o pagpindot sa isang proseso
limitasyon sa laki ng file.
Halimbawa, para ma-trap ang isang interrupt na signal, mag-set up ng handler tulad nito:
aming $shucks;
sub catch_zap {
aking $signname = shift;
$shucks++;
mamatay "May nagpadala sa akin ng SIG$signname";
}
$SIG{INT} = __PACKAGE__ . "::catch_zap";
$SIG{INT} = \&catch_zap; # pinakamahusay na diskarte
Bago ang Perl 5.8.0, kailangan mong gawin ang pinakamaliit na magagawa mo sa iyong
handler; pansinin kung paano ang lahat ng ginagawa namin ay magtakda ng isang pandaigdigang variable at pagkatapos ay magtaas ng isang pagbubukod.
Iyon ay dahil sa karamihan ng mga system, ang mga aklatan ay hindi muling pumasok; lalo na, memorya
allocation at I/O routines ay hindi. Nangangahulugan iyon na ginagawa halos anumang bagay sa iyong
handler sa teorya ay maaaring mag-trigger ng memory fault at kasunod na core dump - tingnan ang "Ipinagpaliban
Mga Senyales (Mga Ligtas na Senyales)" sa ibaba.
Ang mga pangalan ng mga signal ay ang mga nakalista sa pamamagitan ng "kill -l" sa iyong system, o maaari mong
kunin ang mga ito gamit ang CPAN module IPC::Signal.
Maaari mo ring piliing italaga ang mga string na "IGNORE" o "DEFAULT" bilang handler, kung saan
kaso susubukan ni Perl na itapon ang signal o gawin ang default na bagay.
Sa karamihan ng mga platform ng Unix, ang signal na "CHLD" (minsan ay kilala rin bilang "CLD") ay espesyal
pag-uugali na may paggalang sa isang halaga ng "IGNORE". Ang pagtatakda ng $SIG{CHLD} sa "IGNORE" sa naturang a
platform ay may epekto ng hindi paglikha ng mga proseso ng zombie kapag nabigo ang proseso ng magulang
"wait()" sa mga proseso ng bata nito (ibig sabihin, awtomatikong inaani ang mga proseso ng bata). Tumatawag
Ang "wait()" na may $SIG{CHLD} na nakatakda sa "IGNORE" ay karaniwang nagbabalik ng "-1" sa mga naturang platform.
Ang ilang mga signal ay hindi maaaring ma-trap o hindi pinansin, tulad ng KILL at STOP (ngunit hindi ang
TSTP) signal. Tandaan na ang pagwawalang-bahala sa mga signal ay naglalaho sa kanila. Kung gusto mo lang sila
pansamantalang na-block nang hindi sila nawawala kailangan mong gumamit ng sigprocmask ng POSIX.
Ang pagpapadala ng signal sa isang negatibong process ID ay nangangahulugan na ipapadala mo ang signal sa kabuuan
Grupo ng proseso ng Unix. Ang code na ito ay nagpapadala ng isang hang-up na signal sa lahat ng mga proseso sa kasalukuyang
pangkat ng proseso, at itinatakda din ang $SIG{HUP} sa "IGNORE" para hindi nito mapatay ang sarili:
# block na saklaw para sa lokal
{
lokal na $SIG{HUP} = "IGNORE";
patayin ang HUP => -$$;
# nakakatuwang pagsulat ng: kill("HUP", -$$)
}
Ang isa pang kawili-wiling signal na ipapadala ay ang signal number zero. Hindi talaga ito nakakaapekto sa a
proseso ng bata, ngunit sa halip ay suriin kung ito ay buhay o binago ang mga UID nito.
maliban kung (kill 0 => $kid_pid) {
babalaan ang "may masamang nangyari kay $kid_pid";
}
Maaaring mabigo ang signal number zero dahil wala kang pahintulot na ipadala ang signal kapag nakadirekta
sa isang proseso na ang tunay o naka-save na UID ay hindi kapareho ng tunay o epektibong UID ng
proseso ng pagpapadala, kahit na ang proseso ay buhay. Maaari mong matukoy ang dahilan
ng pagkabigo gamit ang $! o "%!".
maliban kung (kill(0 => $pid) || $!{EPERM}) {
babalaan ang "$pid mukhang patay";
}
Baka gusto mo ring gumamit ng mga anonymous na function para sa mga simpleng humahawak ng signal:
$SIG{INT} = sub { mamatay "\nUmalis dito!\n" };
Ang mga humahawak ng SIGCHLD ay nangangailangan ng ilang espesyal na pangangalaga. Kung ang pangalawang anak ay namatay habang nasa signal
handler na dulot ng unang pagkamatay, hindi na tayo makakakuha ng isa pang signal. Kaya dapat umikot dito pa tayo
iiwan ang hindi pa naaani na bata bilang isang zombie. At sa susunod na mamatay ang dalawang bata ay makukuha natin
isa pang zombie. At iba pa.
gamitin ang POSIX ":sys_wait_h";
$SIG{CHLD} = sub {
while ((my $child = waitpid(-1, WNOHANG)) > 0) {
$Kid_Status{$child} = $?;
}
};
# gumawa ng bagay na nakakasira...
Mag-ingat ka: qx(), sistema(), at ang ilang mga module para sa pagtawag sa mga panlabas na utos ay gumagawa ng a tinidor (),
pagkatapos maghintay () para sa resulta. Kaya, tatawagin ang iyong signal handler. kasi maghintay () ay
tinawag na ni sistema() or qx(), ang maghintay () sa signal handler ay hindi na makikita
mga zombie at samakatuwid ay haharang.
Ang pinakamahusay na paraan upang maiwasan ang isyung ito ay ang paggamit waitpid(), tulad ng sa sumusunod na halimbawa:
gamitin ang POSIX ":sys_wait_h"; # para sa walang harang na pagbabasa
mga anak ko;
$SIG{CHLD} = sub {
# huwag mong baguhin ang $! at $? handler sa labas
lokal ($!, $?);
habang ( (my $pid = waitpid(-1, WNOHANG)) > 0 ) {
tanggalin ang $children{$pid};
cleanup_child($pid, $?);
}
};
habang (1) {
aking $pid = tinidor();
mamatay "hindi maaaring tinidor" maliban kung tinukoy $pid;
kung ($pid == 0) {
# ...
exit 0;
} Iba pa {
$bata{$pid}=1;
# ...
system($utos);
# ...
}
}
Ginagamit din ang paghawak ng signal para sa mga timeout sa Unix. Habang ligtas na protektado sa loob ng isang
"eval{}" block, nagtakda ka ng signal handler para ma-trap ang mga signal ng alarm at pagkatapos ay mag-iskedyul na magkaroon
isang naihatid sa iyo sa ilang segundo. Pagkatapos ay subukan ang iyong pagharang na operasyon,
ni-clear ang alarm kapag tapos na ito ngunit hindi bago ka lumabas sa iyong "eval{}" block. Kung ito
aalis, gagamitin mo mamatay () para tumalon sa block.
Narito ang isang halimbawa:
my $ALARM_EXCEPTION = "mag-restart ang alarm clock";
eval {
lokal na $SIG{ALRM} = sub { mamatay $ALARM_EXCEPTION };
alarma 10;
kawan(FH, 2) # blocking write lock
|| mamatay "hindi maaaring dumagsa: $!";
alarma 0;
};
kung ($@ && $@ !~ quotemeta ($ALARM_EXCEPTION)) { mamatay }
Kung ang operasyon ay nag-time out ay sistema() or qx(), ang diskarteng ito ay may pananagutan na makabuo
mga zombie. Kung mahalaga ito sa iyo, kakailanganin mong gawin ang iyong sarili tinidor () at exec(), at pumatay
ang maling proseso ng bata.
Para sa mas kumplikadong paghawak ng signal, maaari mong makita ang karaniwang POSIX module. Nakakalungkot,
ito ay halos ganap na hindi dokumentado, ngunit ang t/lib/posix.t file mula sa pinagmumulan ng Perl
pamamahagi ay may ilang mga halimbawa sa loob nito.
Pag-asikaso ang FOLLOW UP Senyas in Mga demonyo
Isang proseso na karaniwang nagsisimula kapag nag-boot ang system at nagsasara kapag nakasara ang system
down ay tinatawag na isang daemon (Disk And Execution MONitor). Kung ang isang proseso ng daemon ay may a
configuration file na binago pagkatapos masimulan ang proseso, dapat mayroong a
paraan upang sabihin sa prosesong iyon na basahin muli ang configuration file nito nang hindi humihinto sa proseso.
Maraming daemon ang nagbibigay ng mekanismong ito gamit ang isang "SIGHUP" signal handler. Kapag gusto mong sabihin
ang daemon upang muling basahin ang file, ipadala lamang ang "SIGHUP" na signal.
Ang sumusunod na halimbawa ay nagpapatupad ng isang simpleng daemon, na nagre-restart sa sarili tuwing ang
"SIGHUP" signal ay natanggap. Ang aktwal na code ay matatagpuan sa subroutine na "code()", na
nagpi-print lang ng ilang impormasyon sa pag-debug upang ipakita na gumagana ito; dapat palitan ito ng tunay
code.
#!/usr/bin/perl
gumamit ng mahigpit;
gumamit ng mga babala;
gumamit ng POSIX ();
gamitin ang FindBin ();
gamitin ang File::Basename ();
gamitin ang File::Spec::Functions qw(catfile);
$| = 1;
# gawin ang daemon cross-platform, kaya palaging tinatawag ng exec ang script
# mismo na may tamang landas, kahit paano ginamit ang script.
my $script = File::Basename::basename($0);
my $SELF = catfile($FindBin::Bin, $script);
Na-unmask ng # POSIX ang sigprocmask nang maayos
$SIG{HUP} = sub {
print "nakuha SIGHUP\n";
exec($SELF, @ARGV) || mamatay "$0: hindi makapag-restart: $!";
};
code();
sub code {
i-print ang "PID: $$\n";
i-print ang "ARGV: @ARGV\n";
ang aking $count = 0;
habang (1) {
matulog 2;
print ++$count, "\n";
}
}
Ipinagpaliban Signal (Ligtas Mga signal)
Bago ang Perl 5.8.0, ang pag-install ng Perl code upang harapin ang mga signal na naglantad sa iyo sa panganib mula sa
dalawang bagay. Una, ilang function ng system library ang muling pumasok. Kung naputol ang signal
habang ang Perl ay nagsasagawa ng isang function (tulad ng malloc(3) o printf(3)), at ang iyong signal
pagkatapos ay tatawagin muli ng handler ang parehong function, maaari kang makakuha ng hindi mahuhulaan na pag-uugali--kadalasan, a
core dump. Pangalawa, si Perl ay hindi mismo muling pumasok sa pinakamababang antas. Kung ang signal
naaabala ang Perl habang binabago ni Perl ang sarili nitong mga panloob na istruktura ng data, sa parehong paraan
maaaring magresulta ang hindi inaasahang pag-uugali.
Mayroong dalawang bagay na maaari mong gawin, alam mo ito: maging paranoid o maging pragmatic. Ang
Paranoid na diskarte ay gawin ang pinakamaliit hangga't maaari sa iyong signal handler. Magtakda ng umiiral na
integer variable na mayroon nang value, at bumalik. Hindi ito makakatulong sa iyo kung ikaw ay nasa
isang mabagal na tawag sa system, na magre-restart lang. Nangangahulugan iyon na kailangan mong "mamatay" sa longjmp(3)
labas ng handler. Kahit na ito ay isang maliit na cavalier para sa tunay na paranoya, na umiiwas
"mamatay" sa isang handler kasi ang sistema is lumabas para kunin ka. Ang pragmatikong diskarte ay sa
sabihin "Alam ko ang mga panganib, ngunit mas gusto ang kaginhawahan", at gawin ang anumang gusto mo sa iyong
signal handler, at maging handa na linisin ang mga core dump paminsan-minsan.
Ang Perl 5.8.0 at mas bago ay maiwasan ang mga problemang ito sa pamamagitan ng "pagpapaliban" ng mga signal. Ibig sabihin, kapag ang
Ang signal ay inihatid sa proseso ng system (sa C code na nagpapatupad ng Perl) a
flag ay nakatakda, at ang handler ay bumalik kaagad. Pagkatapos sa madiskarteng "ligtas" na mga punto sa
Perl interpreter (hal. kapag malapit nang magsagawa ng bagong opcode) ang mga flag ay sinusuri at
ang Perl level handler mula sa %SIG ay naisakatuparan. Ang "deferred" scheme ay nagbibigay-daan sa higit pa
kakayahang umangkop sa coding ng mga humahawak ng signal tulad ng alam natin na ang Perl interpreter ay nasa isang ligtas
estado, at wala tayo sa function ng system library kapag tinawag ang handler.
Gayunpaman ang pagpapatupad ay naiiba mula sa nakaraang Perls sa mga sumusunod na paraan:
Mga matagal nang opcode
Habang ang Perl interpreter ay tumitingin lamang sa mga signal flag kapag ito ay malapit nang magsagawa ng bago
opcode, isang senyas na dumarating sa panahon ng isang matagal na tumatakbong opcode (hal. isang regular na expression
operasyon sa isang napakalaking string) ay hindi makikita hanggang sa makumpleto ang kasalukuyang opcode.
Kung ang isang signal ng anumang partikular na uri ay gumagana nang maraming beses sa panahon ng isang opcode (tulad ng mula sa a
fine-grained timer), isang beses lang tatawag ang handler para sa signal na iyon, pagkatapos ng
nakumpleto ang opcode; lahat ng iba pang pagkakataon ay itatapon. Higit pa rito, kung ang iyong
ang signal queue ng system ay binabaha hanggang sa punto na may mga signal na
itinaas ngunit hindi pa nahuhuli (at sa gayon ay hindi ipinagpaliban) sa oras na makumpleto ang isang opcode,
ang mga signal na iyon ay maaaring mahuli at ipagpaliban sa mga kasunod na opcode, na may
minsan nakakagulat na mga resulta. Halimbawa, maaari kang makakita ng mga alarm na inihatid kahit na pagkatapos
pagtatawag alarma(0) dahil ang huli ay huminto sa pagtataas ng mga alarma ngunit hindi kinansela ang
paghahatid ng mga alarma na itinaas ngunit hindi pa nahuhuli. Huwag dumedepende sa ugali
inilarawan sa talatang ito dahil ang mga ito ay mga side effect ng kasalukuyang pagpapatupad at
maaaring magbago sa mga hinaharap na bersyon ng Perl.
Nakakaabala sa IO
Kapag ang isang signal ay naihatid (hal., SIGINT mula sa isang control-C) ang operating system ay masisira
sa mga operasyon ng IO tulad ng basahin(2), na ginagamit upang ipatupad ang Perl's Basahin ang linya()
function, ang "<>" operator. Sa mas lumang Perls ay tinawag kaagad ang handler (at bilang
Ang "basahin" ay hindi "hindi ligtas", ito ay gumana nang maayos). Gamit ang "deferred" scheme ang handler ay
hindi tumawag kaagad, at kung ginagamit ni Perl ang library ng "stdio" ng system na library na iyon
maaaring i-restart ang "basahin" nang hindi bumabalik sa Perl upang bigyan ito ng pagkakataong tawagan ang %SIG
handler. Kung nangyari ito sa iyong system ang solusyon ay gamitin ang layer na ":perlio" sa
gawin ang IO--kahit man lang sa mga handle na gusto mong ma-break gamit ang mga signal.
(Sinusuri ng layer na ":perlio" ang mga signal flag at tumatawag sa %SIG handler bago magpatuloy
operasyon ng IO.)
Ang default sa Perl 5.8.0 at mas bago ay awtomatikong gamitin ang layer na ":perlio".
Tandaan na hindi ipinapayong i-access ang isang file handle sa loob ng isang signal handler kung saan
naantala ng signal na iyon ang isang I/O operation sa parehong handle na iyon. Habang ang perl ay nasa
kahit na subukan nang husto upang hindi mag-crash, walang mga garantiya ng integridad ng data; Halimbawa,
ang ilang data ay maaaring malaglag o maisulat nang dalawang beses.
Ang ilang mga function ng networking library tulad ng gethostbyname() ay kilala na may sariling
mga pagpapatupad ng mga timeout na maaaring sumalungat sa iyong mga timeout. Kung mayroon kang
mga problema sa mga naturang function, subukang gamitin ang POSIX sigaction() function, na lumalampas
Perl ligtas na mga signal. Maging babala na ito ay sumasailalim sa iyo sa posibleng memorya
katiwalian, tulad ng inilarawan sa itaas.
Sa halip na itakda ang $SIG{ALRM}:
lokal na $SIG{ALRM} = sub { mamatay "alarm" };
subukan ang isang bagay tulad ng sumusunod:
gumamit ng POSIX qw(SIGALRM);
POSIX::sigaction(SIGALRM, POSIX::SigAction->bago(sub { mamatay "alarm" }))
|| mamatay "Error setting SIGALRM handler: $!\n";
Ang isa pang paraan upang hindi paganahin ang ligtas na pag-uugali ng signal sa lokal ay ang paggamit ng
"Perl::Unsafe::Signals" na module mula sa CPAN, na nakakaapekto sa lahat ng signal.
Maaaring i-restart ang mga tawag sa system
Sa mga system na sumuporta dito, ginamit ng mga mas lumang bersyon ng Perl ang SA_RESTART flag kung kailan
pag-install ng %SIG handler. Nangangahulugan ito na magpapatuloy ang mga restartable system call
sa halip na bumalik kapag may dumating na signal. Upang makapaghatid ng mga ipinagpaliban na signal
kaagad, ginagawa ng Perl 5.8.0 at mas bago hindi gamitin ang SA_RESTART. Dahil dito, maaaring i-restart
maaaring mabigo ang mga system call (na may $! na nakatakda sa "EINTR") sa mga lugar kung saan sila dati
ay nagtagumpay.
Sinusubukang muli ng default na layer na ":perlio" ang "read", "write" at "close" tulad ng inilarawan sa itaas;
Ang mga naantala na "wait" at "waitpid" na tawag ay palaging susubukang muli.
Mga senyales bilang "mga pagkakamali"
Ang ilang mga signal tulad ng SEGV, ILL, at BUS ay nabuo sa pamamagitan ng virtual memory addressing
mga pagkakamali at katulad na "mga pagkakamali". Ang mga ito ay karaniwang nakamamatay: mayroong maliit na antas ng Perl
Handler ay maaaring gawin sa kanila. Kaya inihatid sila kaagad ni Perl sa halip na subukan
ipagpaliban sila.
Mga signal na na-trigger ng estado ng operating system
Sa ilang mga operating system, ang ilang mga tagapangasiwa ng signal ay dapat na "gumawa ng isang bagay"
bago bumalik. Ang isang halimbawa ay maaaring CHLD o CLD, na nagpapahiwatig ng proseso ng bata
nakumpleto. Sa ilang mga operating system ang signal handler ay inaasahang "maghintay" para sa
natapos ang proseso ng bata. Sa ganitong mga sistema hindi gagana ang deferred signal scheme
ang mga senyales na iyon: hindi nito ginagawa ang "paghihintay". Muli ang kabiguan ay magmumukhang isang loop bilang
ang operating system ay muling maglalabas ng signal dahil may nakumpletong bata
mga prosesong hindi pa "hinihintay".
Kung gusto mong ibalik ang dating gawi ng signal sa kabila ng posibleng pagkasira ng memorya, itakda ang
environment variable na "PERL_SIGNALS" hanggang "hindi ligtas". Ang tampok na ito ay unang lumitaw sa Perl
5.8.1.
Pinangalanan Pipa
Ang pinangalanang pipe (madalas na tinutukoy bilang FIFO) ay isang lumang mekanismo ng Unix IPC para sa mga proseso
pakikipag-usap sa parehong makina. Gumagana ito tulad ng mga regular na anonymous na tubo, maliban
na ang mga proseso ay nagtatagpo gamit ang isang filename at hindi kailangang nauugnay.
Upang lumikha ng pinangalanang pipe, gamitin ang function na "POSIX::mkfifo()".
gumamit ng POSIX qw(mkfifo);
mkfifo($path, 0700) || mamatay "nabigo ang mkfifo $path: $!";
Maaari mo ring gamitin ang utos ng Unix mknod(1), o sa ilang sistema, mkfifo(1). Maaaring hindi ang mga ito
maging sa iyong normal na landas, bagaman.
# system return val ay paurong, kaya && hindi ||
#
$ENV{PATH} .=":/ atbp:/usr/etc";
if ( system("mknod", $path, "p")
&& system("mkfifo", $path) )
{
mamatay "mk{nod,fifo} $path failed";
}
Ang fifo ay maginhawa kapag gusto mong ikonekta ang isang proseso sa isang hindi nauugnay. kapag ikaw
buksan ang isang fifo, ang programa ay haharang hanggang sa mayroong isang bagay sa kabilang dulo.
Halimbawa, sabihin nating gusto mong makuha ang iyong .pirma file ay isang pinangalanang pipe na mayroong a
Perl program sa kabilang dulo. Ngayon sa tuwing anumang programa (tulad ng isang mailer, news reader,
finger program, atbp.) ay sumusubok na magbasa mula sa file na iyon, babasahin ng programa sa pagbabasa ang bago
lagda mula sa iyong programa. Gagamitin namin ang pipe-checking file-test operator, -p, Hanapin
out kung sinuman (o anuman) ang aksidenteng naalis ang aming fifo.
chdir(); # umuwi kana
aking $FIFO = ".signature";
habang (1) {
maliban kung (-p $FIFO) {
i-unlink ang $FIFO; # itapon ang anumang kabiguan, mahuhuli sa ibang pagkakataon
nangangailangan ng POSIX; # naantalang pag-load ng mabigat na module
POSIX::mkfifo($FIFO, 0700)
|| mamatay "hindi mkfifo $FIFO: $!";
}
Ang # susunod na linya ay humaharang hanggang sa may mambabasa
bukas (FIFO, "> $FIFO") || mamatay "hindi mabuksan ang $FIFO: $!";
i-print ang FIFO "John Smith (smith\@host.org)\n", `fortune -s`;
malapit(FIFO) || mamatay "hindi maisara ang $FIFO: $!";
matulog 2; # para maiwasan ang mga dup signal
}
paggamit bukas() para IPC
Pangunahin ni Perl bukas() ang pahayag ay maaari ding gamitin para sa unidirectional interprocess
komunikasyon sa pamamagitan ng pagdaragdag o paglalagay ng simbolo ng pipe sa pangalawang argumento sa
bukas(). Narito kung paano magsimula ng isang bagay sa proseso ng bata na balak mong sulatan:
open(SPOOLER, "| cat -v | lpr -h 2>/dev/null")
|| mamatay "hindi maaaring tinidor: $!";
lokal na $SIG{PIPE} = sub { mamatay "nasira ang spooler pipe" };
i-print ang SPOOLER "bagay\n";
isara ang SPOOLER || mamatay "masamang spool: $! $?";
At narito kung paano simulan ang proseso ng bata kung saan mo gustong basahin:
bukas(STATUS, "netstat -an 2>&1 |")
|| mamatay "hindi maaaring tinidor: $!";
habang ( ) {
susunod kung /^(tcp|udp)/;
i-print;
}
malapit na STATUS || mamatay "masamang netstat: $! $?";
Kung ang isa ay makatitiyak na ang isang partikular na programa ay isang Perl script na umaasa sa mga filename
@ARGV, ang matalinong programmer ay maaaring sumulat ng ganito:
% program f1 "cmd1|" - f2 "cmd2|" f3 < tmpfile
at kahit anong uri ng shell ito ay tinawag, ang Perl program ay magbabasa mula sa
file f1, ang proseso cmd1, karaniwang input (tmpfile sa kasong ito), ang f2 file, ang cmd2
utos, at panghuli ang f3 file. Medyo maganda, eh?
Maaari mong mapansin na maaari kang gumamit ng mga backtick para sa halos kaparehong epekto ng pagbubukas ng tubo
para sa pagbabasa:
print grep { !/^(tcp|udp)/ } `netstat -an 2>&1`;
mamatay "bad netstatus ($?)" kung $?;
Bagama't totoo ito sa ibabaw, mas mahusay na iproseso ang file sa isang linya
o i-record sa isang pagkakataon dahil pagkatapos ay hindi mo na kailangang basahin ang buong bagay sa memorya sa
minsan. Nagbibigay din ito sa iyo ng mas pinong kontrol sa buong proseso, na hinahayaan kang patayin ang
maagang proseso ng bata kung gusto mo.
Mag-ingat na suriin ang mga halaga ng pagbabalik mula sa pareho bukas() at isara (). Kung ikaw ay pagsulat sa
isang tubo, dapat mo ring i-trap ang SIGPIPE. Kung hindi, isipin kung ano ang mangyayari kapag nagsimula ka
isang pipe sa isang command na hindi umiiral: ang bukas() ay sa lahat ng posibilidad na magtagumpay (ito lamang
sumasalamin sa tinidor ()'s tagumpay), ngunit pagkatapos ay mabibigo ang iyong output--kahanga-hanga. Hindi kaya ni Perl
alamin kung gumagana ang utos, dahil ang iyong utos ay aktwal na tumatakbo sa isang hiwalay
proseso kung kaninong exec() baka nabigo. Samakatuwid, habang bumabalik ang mga mambabasa ng mga huwad na utos
sa isang mabilis na EOF, ang mga manunulat sa mga huwad na utos ay tatamaan ng isang senyales, na mas makabubuti
maging handa sa paghawak. Isaalang-alang:
open(FH, "|bogus") || mamatay "hindi maaaring tinidor: $!";
i-print ang FH "bang\n"; # hindi kinakailangan o sapat
# upang suriin ang pag-print ng retval!
malapit(FH) || mamatay "hindi maisara: $!";
Ang dahilan ng hindi pagsuri sa return value mula sa i-print () ay dahil sa pipe buffering;
naantala ang pisikal na pagsusulat. Iyan ay hindi sasabog hanggang sa malapit, at ito ay sasabog
isang SIGPIPE. Upang mahuli ito, maaari mong gamitin ito:
$SIG{PIPE} = "IGNORE";
open(FH, "|bogus") || mamatay "hindi maaaring tinidor: $!";
i-print ang FH "bang\n";
malapit(FH) || mamatay "hindi maisara: status=$?";
Mga filehandle
Parehong ang pangunahing proseso at sinumang bata na pinoproseso nito ay may parehong STDIN, STDOUT, at
STDERR filehandles. Kung sinubukan ng parehong proseso na i-access ang mga ito nang sabay-sabay, magagawa ng mga kakaibang bagay
mangyari. Maaari mo ring isara o muling buksan ang mga filehandle para sa bata. Makukuha mo
sa paligid nito sa pamamagitan ng pagbubukas ng iyong tubo gamit ang bukas(), ngunit sa ilang mga sistema nangangahulugan ito na ang
Ang proseso ng bata ay hindi mabubuhay sa magulang.
likuran Mga Proseso
Maaari kang magpatakbo ng isang command sa background na may:
system("cmd &");
Ang STDOUT at STDERR ng command (at posibleng STDIN, depende sa iyong shell) ay ang
katulad ng sa magulang. Hindi mo na kakailanganing mahuli ang SIGCHLD dahil sa double-fork taking
lugar; tingnan sa ibaba para sa mga detalye.
Matapos Dissociation of Anak mula Magulang
Sa ilang mga kaso (pagsisimula ng mga proseso ng server, halimbawa) gugustuhin mong ganap
ihiwalay ang proseso ng bata sa magulang. Ito ay madalas na tinatawag na daemonization. A
well-behaved daemon ay din chdir() sa root directory para hindi nito maiwasan
i-unmount ang filesystem na naglalaman ng direktoryo kung saan ito inilunsad, at
i-redirect ang mga karaniwang file descriptor nito mula sa at papunta / dev / null upang ang random na output ay hindi
huminto sa terminal ng gumagamit.
gamitin ang POSIX "setsid";
subdemonize {
chdir("/") || mamatay "hindi maaaring chdir sa /: $!";
bukas(STDIN, "< /dev/null") || mamatay "hindi mabasa /dev/null: $!";
bukas(STDOUT, "> /dev/null") || mamatay "hindi magsulat sa /dev/null: $!";
tinukoy(aking $pid = tinidor()) || mamatay "hindi maaaring tinidor: $!";
lumabas kung $pid; # non-zero ngayon ay nangangahulugan na ako ang magulang
(setsid() != -1) || mamatay "Hindi makapagsimula ng bagong session: $!";
bukas(STDERR, ">&STDOUT") || mamatay "hindi maaaring dup stdout: $!";
}
Ang tinidor () kailangang dumating bago ang setsid() upang matiyak na hindi ka isang pinuno ng pangkat ng proseso;
ang setsid() mabibigo kung ikaw ay. Kung ang iyong system ay walang setsid() function,
buksan /dev/tty at gamitin ang "TIOCNOTTY" ioctl() sa halip. Tingnan mo tty(4) para sa mga detalye.
Dapat suriin ng mga user na hindi Unix ang kanilang "Your_OS::Proseso" module para sa iba pang posibleng solusyon.
Ligtas Pipa Binubuksan
Ang isa pang kawili-wiling diskarte sa IPC ay ang paggawa ng iyong solong programa na maging multiprocess at
makipag-usap sa pagitan--o maging sa pagitan ng--iyong mga sarili. Ang bukas() function ay tatanggap ng isang file
argumento ng alinman sa "-|" o "|-" upang gumawa ng isang napaka-kawili-wiling bagay: tinidor nito ang isang bata na konektado
sa filehandle na iyong binuksan. Ang bata ay nagpapatakbo ng parehong programa bilang ang magulang.
Ito ay kapaki-pakinabang para sa ligtas na pagbubukas ng isang file kapag tumatakbo sa ilalim ng isang ipinapalagay na UID o GID, para sa
halimbawa. Kung magbubukas ka ng tubo sa minus, maaari kang sumulat sa filehandle na iyong binuksan at sa iyong
hahanapin ito ng bata kaniya STDIN. Kung magbubukas ka ng tubo mula minus, maaari mong basahin mula sa
filehandle binuksan mo ang kahit anong sulatan ng iyong anak kaniya STDOUT.
Gumamit ng Ingles;
my $PRECIOUS = "/path/to/some/safe/file";
ang aking $sleep_count;
aking $pid;
gawin
$pid = bukas(KID_TO_WRITE, "|-");
maliban kung (tinukoy $pid) {
bigyan ng babala "hindi maaaring tinidor: $!";
mamatay "bailing out" kung $sleep_count++ > 6;
matulog 10;
}
} hanggang sa matukoy ang $pid;
kung ($pid) { # Ako ang magulang
print KID_TO_WRITE @some_data;
malapit(KID_TO_WRITE) || bigyan ng babala ang "kid exit $?";
} else { # ako ang bata
# drop permissions sa setuid at/o setgid na mga programa:
($EUID, $EGID) = ($UID, $GID);
bukas (OUTFILE, "> $PRECIOUS")
|| mamatay "hindi mabuksan ang $PRECIOUS: $!";
habang ( ) {
i-print ang OUTFILE; Ang STDIN ng # anak ay KID_TO_WRITE ng magulang
}
malapit(OUTFILE) || mamatay "hindi maisara ang $PRECIOUS: $!";
lumabas(0); #wag kalimutan ito!!
}
Ang isa pang karaniwang gamit para sa construct na ito ay kapag kailangan mong magsagawa ng isang bagay nang walang
panghihimasok ng shell. Sa sistema(), ito ay diretso, ngunit hindi ka maaaring gumamit ng bukas na tubo
o mga backtick nang ligtas. Iyon ay dahil walang paraan upang pigilan ang shell mula sa pagkuha nito
kamay sa iyong mga argumento. Sa halip, gumamit ng mas mababang antas ng kontrol upang tumawag exec() direkta.
Narito ang isang ligtas na backtick o pipe na bukas para basahin:
my $pid = open(KID_TO_READ, "-|");
tinukoy($pid) || mamatay "hindi maaaring tinidor: $!";
kung ($pid) { # magulang
habang ( ) {
# gumawa ng isang bagay na kawili-wili
}
malapit(KID_TO_READ) || bigyan ng babala ang "kid exit $?";
} iba { # anak
($EUID, $EGID) = ($UID, $GID); # suid lang
exec($program, @options, @args)
|| mamatay "hindi maipatupad ang programa: $!";
# NAHIHILIG
}
At narito ang isang ligtas na tubo na bukas para sa pagsusulat:
my $pid = open(KID_TO_WRITE, "|-");
tinukoy($pid) || mamatay "hindi maaaring tinidor: $!";
$SIG{PIPE} = sub { mamatay "whoops, $program pipe nasira"};
kung ($pid) { # magulang
print KID_TO_WRITE @data;
malapit(KID_TO_WRITE) || bigyan ng babala ang "kid exit $?";
} iba { # anak
($EUID, $EGID) = ($UID, $GID);
exec($program, @options, @args)
|| mamatay "hindi maipatupad ang programa: $!";
# NAHIHILIG
}
Napakadaling i-dead-lock ang isang proseso gamit ang form na ito ng bukas(), o sa anumang gamit
of tubo() na may maraming mga subprocess. Ang halimbawa sa itaas ay "ligtas" dahil ito ay simple
at tumatawag exec(). Tingnan ang "Pag-iwas sa Mga Deadlock ng Pipe" para sa pangkalahatang mga prinsipyo sa kaligtasan, ngunit doon
ay mga karagdagang gotcha na may Safe Pipe Opens.
Sa partikular, kung binuksan mo ang tubo gamit ang "bukas na FH, "|-"", hindi mo basta-basta magagamit
isara () sa proseso ng magulang upang isara ang isang hindi gustong manunulat. Isaalang-alang ang code na ito:
my $pid = open(WRITER, "|-"); # tinidor buksan ang isang bata
tinukoy($pid) || mamatay "nabigo ang unang tinidor: $!";
kung ($pid) {
kung (aking $sub_pid = tinidor()) {
tinukoy($sub_pid) || mamatay "ang pangalawang tinidor ay nabigo: $!";
malapit na(WRITER) || mamatay "hindi maisara MANUNULAT: $!";
# ngayon gumawa ng iba...
}
tao {
# unang sumulat sa WRITER
# ...
# tapos pag tapos na
malapit na(WRITER) || mamatay "hindi maisara MANUNULAT: $!";
lumabas(0);
}
}
tao {
# gumawa muna ng isang bagay sa STDIN, pagkatapos
lumabas(0);
}
Sa halimbawa sa itaas, ang tunay na magulang ay hindi gustong sumulat sa WRITER filehandle, kaya
isinara ito. Gayunpaman, dahil binuksan ang WRITER gamit ang "open FH, "|-"", mayroon itong espesyal
pag-uugali: pagsasara ng mga tawag waitpid() (tingnan ang "waitpid" sa perlfunc), na naghihintay para sa
subprocess para lumabas. Kung ang proseso ng bata ay magtatapos sa paghihintay para sa isang bagay na nangyayari sa
seksyon na may markang "gawin ang iba", mayroon kang deadlock.
Maaari rin itong maging problema sa mga intermediate na subprocess sa mas kumplikadong code, na
tatawag waitpid() sa lahat ng bukas na filehandle sa panahon ng pandaigdigang pagkawasak--na hindi mahuhulaan
order.
Upang malutas ito, dapat mong gamitin nang manu-mano tubo(), tinidor (), at ang anyo ng bukas() na nagtatakda ng isa
file descriptor sa isa pa, tulad ng ipinapakita sa ibaba:
pipe(READER, WRITER) || mamatay "pipe failed: $!";
$pid = tinidor();
tinukoy($pid) || mamatay "nabigo ang unang tinidor: $!";
kung ($pid) {
malapit READER;
kung (aking $sub_pid = tinidor()) {
tinukoy($sub_pid) || mamatay "nabigo ang unang tinidor: $!";
malapit na(WRITER) || mamatay "hindi maisara MANUNULAT: $!";
}
tao {
# sumulat sa WRITER...
# ...
# tapos pag tapos na
malapit na(WRITER) || mamatay "hindi maisara MANUNULAT: $!";
lumabas(0);
}
# sumulat sa WRITER...
}
tao {
bukas(STDIN, "<&READER") || mamatay "hindi mabuksan muli ang STDIN: $!";
malapit na(WRITER) || mamatay "hindi maisara MANUNULAT: $!";
# gumawa ng paraan...
lumabas(0);
}
Dahil ang Perl 5.8.0, maaari mo ring gamitin ang form ng listahan ng "bukas" para sa mga tubo. Mas gusto ito
kapag nais mong maiwasan ang pagkakaroon ng shell na bigyang-kahulugan ang mga metacharacter na maaaring nasa iyong
command string.
Kaya halimbawa, sa halip na gamitin ang:
bukas(PS_PIPE, "ps aux|") || mamatay "hindi mabuksan ang ps pipe: $!";
Gagamitin ng isa ang alinman sa mga ito:
bukas(PS_PIPE, "-|", "ps", "aux")
|| mamatay "hindi mabuksan ang ps pipe: $!";
@ps_args = qw[ ps aux ];
bukas(PS_PIPE, "-|", @ps_args)
|| mamatay "hindi mabuksan ang @ps_args|: $!";
Dahil mayroong higit sa tatlong argumento sa bukas(), tinidor ang ps(1) utos wala
nag-spawning ng shell, at binabasa ang karaniwang output nito sa pamamagitan ng "PS_PIPE" filehandle. Ang
kaukulang syntax sa magsulat upang mag-utos ng mga tubo ay ang paggamit ng "|-" sa halip na "-|".
Ito ay tinatanggap na isang medyo hangal na halimbawa, dahil gumagamit ka ng mga literal na string na kung saan
ganap na ligtas ang nilalaman. Kaya't walang dahilan upang gamitin ang mas mahirap basahin,
multi-argument form ng pipe bukas(). Gayunpaman, sa tuwing hindi ka makatitiyak na ang
Ang mga argumento ng programa ay walang mga shell metacharacter, ang mas mahilig sa anyo ng bukas() ay dapat na
ginamit. Halimbawa:
@grep_args = ("egrep", "-i", $some_pattern, @many_files);
bukas(GREP_PIPE, "-|", @grep_args)
|| mamatay "hindi mabuksan @grep_args|: $!";
Narito ang multi-argument form ng pipe bukas() ay ginustong dahil ang pattern at sa katunayan
kahit na ang mga filename mismo ay maaaring magkaroon ng metacharacter.
Magkaroon ng kamalayan na ang mga operasyong ito ay puno ng Unix forks, na nangangahulugang maaaring hindi tama ang mga ito
ipinatupad sa lahat ng alien system.
Pag-iwas Pipa Mga baliw
Sa tuwing mayroon kang higit sa isang subprocess, dapat kang mag-ingat na ang bawat isa ay magsasara alinman
kalahati ng anumang mga tubo na nilikha para sa interprocess na komunikasyon na hindi nito ginagamit. Ito ay dahil ang
sinumang bata na nagproseso ng pagbabasa mula sa pipe at umaasang hindi ito matatanggap ng EOF, at
samakatuwid ay hindi lalabas. Ang isang proseso ng pagsasara ng tubo ay hindi sapat upang isara ito; ang huli
ang prosesong nakabukas ang tubo ay dapat isara ito para mabasa nito ang EOF.
Nakakatulong ang ilang partikular na built-in na feature ng Unix na maiwasan ito sa halos lahat ng oras. Halimbawa,
Ang filehandles ay may flag na "close on exec", na nakatakda en masa nasa ilalim ng kontrol ng $^F
variable. Ito ay kaya ang anumang filehandles na hindi mo tahasang naruta sa STDIN, STDOUT o
STDERR ng isang bata programa ay awtomatikong isasara.
Laging tahasan at agad na tumawag isara () sa nasusulat na dulo ng anumang tubo, maliban kung
ang prosesong iyon ay talagang sumusulat dito. Kahit na hindi ka tahasang tumawag isara (), Perl
ay pa rin isara () lahat ng filehandles sa panahon ng global na pagkasira. Gaya ng naunang tinalakay, kung
ang mga filehandle na iyon ay nabuksan gamit ang Safe Pipe Open, magreresulta ito sa pagtawag
waitpid(), na maaaring muling deadlock.
Patawad Pakikipag-usap sa Isa pa paraan
Bagama't ito ay gumagana nang maayos para sa unidirectional na komunikasyon, paano naman
bidirectional na komunikasyon? Ang pinaka-halatang diskarte ay hindi gumagana:
# HINDI ITO GUMAGANA!!
open(PROG_FOR_READING_AND_WRITING, "| some program |")
Kung nakalimutan mong "gumamit ng mga babala," lubos mong mapapalampas ang kapaki-pakinabang na diagnostic
mensahe:
Hindi magawa ang bidirectional pipe sa -e line 1.
Kung gusto mo talaga, maaari mong gamitin ang pamantayan open2() mula sa module na "IPC::Open2" hanggang
saluhin ang magkabilang dulo. Mayroon ding isang open3() sa "IPC::Open3" para sa tridirectional I/O kaya ikaw
maaari ring mahuli ang STDERR ng iyong anak, ngunit ang paggawa nito ay mangangailangan ng isang awkward piliin()
loop at hindi papayagan kang gumamit ng normal na Perl input operations.
Kung titingnan mo ang pinagmulan nito, makikita mo iyon open2() gumagamit ng mababang antas ng primitives tulad ng
tubo() at exec() syscalls upang lumikha ng lahat ng mga koneksyon. Bagaman maaaring ito ay
mas mahusay sa pamamagitan ng paggamit socketpair(), ito ay hindi gaanong portable kaysa dito
ay na. Ang open2() at open3() ang mga function ay malabong gumana kahit saan maliban sa a
Unix system, o hindi bababa sa isang nagpapanggap na pagsunod sa POSIX.
Narito ang isang halimbawa ng paggamit open2():
gamitin ang FileHandle;
gamitin ang IPC::Open2;
$pid = open2(*Reader, *Writer, "cat -un");
print Writer "bagay\n";
$nakuha = ;
Ang problema dito ay ang buffering ay talagang sisira sa iyong araw. Kahit na
ang iyong "Writer" filehandle ay awtomatikong na-flush kaya ang proseso sa kabilang dulo ay nakapasok sa iyong data
sa isang napapanahong paraan, karaniwan ay hindi ka makakagawa ng anuman upang pilitin ang prosesong iyon na ibigay ang data nito
sa isang katulad na mabilis na paraan. Sa espesyal na kaso na ito, maaari talaga namin, dahil kami
nagbigay pusa a -u bandila upang gawin itong unbuffered. Ngunit napakakaunting mga utos ay idinisenyo upang gumana
sa mga tubo, kaya bihira itong gumana maliban kung ikaw mismo ang sumulat ng programa sa kabilang dulo ng
ang double-ended pipe.
Ang isang solusyon dito ay ang paggamit ng isang library na gumagamit ng mga pseudotty upang gawing kumilos ang iyong programa
mas makatwiran. Sa ganitong paraan hindi mo kailangang magkaroon ng kontrol sa source code ng
program na iyong ginagamit. Tinutugunan din ng "Asahan" na module mula sa CPAN ang ganitong uri ng bagay.
Ang module na ito ay nangangailangan ng dalawa pang module mula sa CPAN, "IO::Pty" at "IO::Stty". Itinatakda nito ang a
pseudo terminal upang makipag-ugnayan sa mga program na nagpipilit na makipag-usap sa terminal device
driver. Kung sinusuportahan ang iyong system, maaaring ito ang iyong pinakamahusay na mapagpipilian.
Patawad Pakikipag-usap sa Iyong sarili
Kung gusto mo, maaari kang gumawa ng mababang antas tubo() at tinidor () syscalls upang i-stitch ito sa pamamagitan ng
kamay. Ang halimbawang ito ay nakikipag-usap lamang sa sarili nito, ngunit maaari mong muling buksan ang naaangkop na mga hawakan
STDIN at STDOUT at tumawag sa ibang mga proseso. (Ang sumusunod na halimbawa ay walang tamang error
sinusuri.)
#!/usr/bin/perl -w
# pipe1 - bidirectional na komunikasyon gamit ang dalawang pares ng tubo
# dinisenyo para sa socketpair-challenged
gumamit ng IO::Handle; # libu-libong linya para lang sa autoflush :-(
pipe(PARENT_RDR, CHILD_WTR); # XXX: suriin ang pagkabigo?
pipe(CHILD_RDR, PARENT_WTR); # XXX: suriin ang pagkabigo?
BATA_WTR->autoflush(1);
PARENT_WTR->autoflush(1);
kung ($pid = tinidor()) {
isara ang PARENT_RDR;
malapit PARENT_WTR;
print CHILD_WTR "Ipinapadala ito ng Parent Pid $$\n";
chomp($line = );
print "Parent Pid $$ basahin mo lang ito: '$line'\n";
isara ang CHILD_RDR; malapit CHILD_WTR;
waitpid($pid, 0);
} Iba pa {
mamatay "hindi maaaring tinidor: $!" maliban kung tinukoy $pid;
isara ang CHILD_RDR;
malapit CHILD_WTR;
chomp($line = );
print "Child Pid $$ basahin mo na lang ito: '$line'\n";
print PARENT_WTR "Ipinapadala ito ng Bata Pid $$\n";
isara ang PARENT_RDR;
malapit PARENT_WTR;
lumabas(0);
}
Ngunit hindi mo talaga kailangang gumawa ng dalawang pipe call. Kung mayroon kang socketpair() sistema
tumawag, gagawin nito ang lahat para sa iyo.
#!/usr/bin/perl -w
# pipe2 - bidirectional na komunikasyon gamit ang socketpair
# "the best ones always go both ways"
gumamit ng Socket;
gumamit ng IO::Handle; # libu-libong linya para lang sa autoflush :-(
# Sinasabi namin ang AF_UNIX dahil bagaman ang *_LOCAL ay ang
# POSIX 1003.1g na anyo ng pare-pareho, maraming makina
# wala pa rin.
socketpair(ANAK, MAGULANG, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
|| mamatay "socketpair: $!";
BATA->autoflush(1);
MAGULANG->autoflush(1);
kung ($pid = tinidor()) {
malapit MAGULANG;
print BATA "Pinapadala ito ng Magulang na Pid $$\n";
chomp($line = );
print "Parent Pid $$ basahin mo lang ito: '$line'\n";
malapit BATA;
waitpid($pid, 0);
} Iba pa {
mamatay "hindi maaaring tinidor: $!" maliban kung tinukoy $pid;
malapit BATA;
chomp($line = );
print "Child Pid $$ basahin mo na lang ito: '$line'\n";
print MAGULANG "Ipinapadala ito ng Bata Pid $$\n";
malapit MAGULANG;
lumabas(0);
}
Mga socket: Client / Server Pakikipag-usap
Bagama't hindi ganap na limitado sa mga operating system na nagmula sa Unix (hal., WinSock sa mga PC
nagbibigay ng suporta sa socket, tulad ng ilang mga aklatan ng VMS), maaaring wala kang mga socket sa iyong
system, kung saan ang seksyong ito ay malamang na hindi makatutulong sa iyo. Sa
socket, maaari mong gawin ang parehong mga virtual na circuit tulad ng mga stream ng TCP at mga datagram tulad ng mga packet ng UDP.
Maaari kang gumawa ng higit pa depende sa iyong system.
Ang mga function ng Perl para sa pagharap sa mga socket ay may parehong mga pangalan tulad ng katumbas
system calls sa C, ngunit ang kanilang mga argumento ay may posibilidad na magkaiba sa dalawang dahilan. Una, Perl
Ang mga filehandle ay gumagana nang iba kaysa sa C file descriptors. Pangalawa, alam na ni Perl ang
haba ng mga string nito, kaya hindi mo kailangang ipasa ang impormasyong iyon.
Isa sa mga pangunahing problema sa sinaunang, antemillennial socket code sa Perl ay iyon
gumamit ng mga hard-coded na halaga para sa ilan sa mga constant, na lubhang nakakapinsala sa portability. kung ikaw
kailanman nakakakita ng code na gumagawa ng anumang bagay tulad ng tahasang pagtatakda ng "$AF_INET = 2", alam mong ikaw ay
para sa malaking problema. Ang isang napakahusay na diskarte ay ang paggamit ng "Socket" na module,
na mas mapagkakatiwalaang nagbibigay ng access sa iba't ibang mga constant at function na kakailanganin mo.
Kung hindi ka nagsusulat ng server/client para sa isang umiiral na protocol tulad ng NNTP o SMTP, ikaw
dapat mag-isip kung paano malalaman ng iyong server kapag natapos na ang kliyente
pakikipag-usap, at kabaliktaran. Karamihan sa mga protocol ay batay sa isang linyang mensahe at tugon (kaya
alam ng isang partido na tapos na ang isa kapag natanggap ang isang "\n") o mga multi-line na mensahe at
mga tugon na nagtatapos sa isang tuldok sa isang walang laman na linya ("\n.\n" ay nagtatapos sa isang mensahe/tugon).
internet Linya terminators
Ang Internet line terminator ay "\015\012". Sa ilalim ng mga variant ng ASCII ng Unix, maaari iyon
karaniwang isinusulat bilang "\r\n", ngunit sa ilalim ng ibang mga system, maaaring minsan ang "\r\n".
"\015\015\012", "\012\012\015", o ibang bagay. Tinukoy ng mga pamantayan
pagsusulat ng "\015\012" upang maging kaayon (maging mahigpit sa iyong ibibigay), ngunit sila rin
Inirerekomenda ang pagtanggap ng nag-iisang "\012" sa input (maging maluwag sa kung ano ang kailangan mo). hindi pa namin
palaging napakahusay tungkol doon sa code sa manpage na ito, ngunit maliban kung ikaw ay nasa isang Mac
from way back in its pre-Unix dark ages, malamang na magiging ok ka.
internet TCP Kliyente at Server
Gumamit ng mga socket ng Internet-domain kapag gusto mong gumawa ng komunikasyon sa client-server na maaaring mangyari
umaabot sa mga makina sa labas ng iyong sariling sistema.
Narito ang isang sample na TCP client na gumagamit ng mga socket ng Internet-domain:
#!/usr/bin/perl -w
gumamit ng mahigpit;
gumamit ng Socket;
my ($remote, $port, $iaddr, $paddr, $proto, $line);
$remote = shift || "localhost";
$port = shift || 2345; # random na port
if ($port =~ /\D/) { $port = getservbyname($port, "tcp") }
mamatay "Walang port" maliban kung $port;
$iaddr = inet_aton($remote) || mamatay "walang host: $remote";
$paddr = sockaddr_in($port, $iaddr);
$proto = getprotobyname("tcp");
socket(SOCK, PF_INET, SOCK_STREAM, $proto) || mamatay "socket: $!";
kumonekta(SOCK, $paddr) || mamatay "kumonekta: $!";
habang ($line = ) {
i-print ang $line;
}
malapit (SOCK) || mamatay "malapit: $!";
lumabas(0);
At narito ang isang kaukulang server upang sumama dito. Iiwan namin ang address bilang
"INADDR_ANY" para mapili ng kernel ang naaangkop na interface sa mga multihomed host.
Kung gusto mong umupo sa isang partikular na interface (tulad ng panlabas na bahagi ng isang gateway o firewall
machine), sa halip ay punan ito ng iyong totoong address.
#!/usr/bin/perl -Tw
gumamit ng mahigpit;
SIMULAN { $ENV{PATH} = "/ usr / bin:/ bin"}
gumamit ng Socket;
gumamit ng Carp;
aking $EOL = "\015\012";
sub logmsg { print "$0 $$: @_ at ", scalar localtime(), "\n" }
aking $port = shift || 2345;
mamatay "invalid port" maliban kung $port =~ /^ \d+ $/x;
my $proto = getprotobyname("tcp");
socket(Server, PF_INET, SOCK_STREAM, $proto) || mamatay "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
|| mamatay "setsoccopt: $!";
bind(Server, sockaddr_in($port, INADDR_ANY)) || mamatay "bind: $!";
makinig(Server, SOMAXCONN) || mamatay "makinig: $!";
logmsg "nagsimula ang server sa port $port";
aking $paddr;
$SIG{CHLD} = \&REAPER;
para sa ( ; $paddr = accept(Client, Server); close Client) {
my($port, $iaddr) = sockaddr_in($paddr);
my $name = gethostbyaddr($iaddr, AF_INET);
logmsg "koneksyon mula sa $name [",
inet_ntoa($iaddr), "]
sa port $port";
print Client "Hello there, $name, it's now ",
scalar localtime(), $EOL;
}
At narito ang isang multitasking na bersyon. Ito ay multitasked sa na tulad ng karamihan sa mga karaniwang server, ito
nangingitlog (tinidor ()s) isang slave server upang pangasiwaan ang kahilingan ng kliyente upang magawa ng master server
mabilis na bumalik sa serbisyo ng isang bagong kliyente.
#!/usr/bin/perl -Tw
gumamit ng mahigpit;
SIMULAN { $ENV{PATH} = "/ usr / bin:/ bin"}
gumamit ng Socket;
gumamit ng Carp;
aking $EOL = "\015\012";
sub spawn; # pasulong na deklarasyon
sub logmsg { print "$0 $$: @_ at ", scalar localtime(), "\n" }
aking $port = shift || 2345;
mamatay "invalid port" maliban kung $port =~ /^ \d+ $/x;
my $proto = getprotobyname("tcp");
socket(Server, PF_INET, SOCK_STREAM, $proto) || mamatay "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
|| mamatay "setsoccopt: $!";
bind(Server, sockaddr_in($port, INADDR_ANY)) || mamatay "bind: $!";
makinig(Server, SOMAXCONN) || mamatay "makinig: $!";
logmsg "nagsimula ang server sa port $port";
ang aking $waitedpid = 0;
aking $paddr;
gamitin ang POSIX ":sys_wait_h";
gumamit ng Errno;
sub REAPER {
lokal na $!; # huwag hayaang ma-overwrite ni waitpid() ang kasalukuyang error
habang ((my $pid = waitpid(-1, WNOHANG)) > 0 && WIFEXITED($?)) {
logmsg "inani $waitedpid" . ($? ? " na may exit $?" : "");
}
$SIG{CHLD} = \&REAPER; # kinasusuklaman ang SysV
}
$SIG{CHLD} = \&REAPER;
habang (1) {
$paddr = tanggapin(Client, Server) || gawin {
# try again if accept() return dahil nakakuha ng signal
susunod kung $!{EINTR};
mamatay "tanggapin: $!";
};
my ($port, $iaddr) = sockaddr_in($paddr);
my $name = gethostbyaddr($iaddr, AF_INET);
logmsg "koneksyon mula sa $name [",
inet_ntoa($iaddr),
"] sa port $port";
spawn sub {
$| = 1;
print "Hello there, $name, it's now ", scalar localtime(), $EOL;
exec "/usr/games/fortune" # XXX: "maling" line terminators
o aminin ang "hindi makapagpatupad ng kapalaran: $!";
};
malapit na Kliyente;
}
sub spawn {
aking $coderef = shift;
maliban kung (@_ == 0 && $coderef && ref($coderef) eq "CODE") {
aminin ang "paggamit: spawn CODEREF";
}
aking $pid;
maliban kung (defined ($pid = fork())) {
logmsg "hindi ma-fork: $!";
bumalik;
}
elsif ($pid) {
logmsg "begat $pid";
bumalik; # Ako ang magulang
}
# else ako ang bata -- go spawn
bukas(STDIN, "<&Client") || mamatay "hindi maaaring dup client sa stdin";
open(STDOUT, ">&Client") || mamatay "hindi maaaring dup client sa stdout";
## bukas(STDERR, ">&STDOUT") || mamatay "hindi maaaring dup stdout sa stderr";
exit($coderef->());
}
Ang server na ito ay tumatagal ng problema upang i-clone ang isang bersyon ng bata sa pamamagitan ng tinidor () para sa bawat papasok
hiling. Sa ganoong paraan makakayanan nito ang maraming kahilingan nang sabay-sabay, na maaaring hindi mo palaging gusto.
Kahit na wala ka tinidor (), ang makinig () papayagan ang maraming nakabinbing koneksyon. Forking
Ang mga server ay kailangang maging partikular na maingat tungkol sa paglilinis ng kanilang mga patay na anak (tinatawag na
"zombies" sa Unix parlance), dahil kung hindi, mabilis mong mapupunan ang iyong talahanayan ng proseso.
Ang REAPER subroutine ay ginagamit dito para tumawag waitpid() para sa anumang proseso ng bata na mayroon
tapos na, sa gayo'y tinitiyak na malinis silang magwawakas at hindi sumasali sa hanay ng
Buhay na patay.
Sa loob ng while loop tinatawagan namin tanggapin () at suriin upang makita kung nagbabalik ito ng maling halaga. Ito
ay karaniwang nagpapahiwatig ng isang error sa system na kailangang iulat. Gayunpaman, ang pagpapakilala ng
mga ligtas na signal (tingnan ang "Mga Deferred Signal (Mga Ligtas na Signal)" sa itaas) sa Perl 5.8.0 ay nangangahulugan na
tanggapin () maaari ding maantala kapag nakatanggap ng signal ang proseso. Ito ay karaniwang
nangyayari kapag ang isa sa mga naka-forked na subprocess ay lumabas at nag-abiso sa parent na proseso gamit ang a
signal ng CHLD.
If tanggapin () ay nagambala ng isang senyas, $! itatakda sa EINTR. Kung mangyari ito, kaya natin
ligtas na magpatuloy sa susunod na pag-ulit ng loop at isa pang tawag sa tanggapin (). Ito ay
mahalaga na hindi baguhin ng iyong signal handling code ang halaga ng $!, o kung hindi ang pagsubok na ito
malamang na mabibigo. Sa REAPER subroutine lumikha kami ng lokal na bersyon ng $! bago tumawag
waitpid(). Kailan waitpid() nagtatakda ng $! kay ECHILD gaya ng hindi maiiwasang gawin kapag wala na
naghihintay ang mga bata, ina-update nito ang lokal na kopya at iniiwan ang orihinal na hindi nagbabago.
Dapat mong gamitin ang -T flag para paganahin ang taint checking (tingnan ang perlsec) kahit na hindi kami
tumatakbo setuid o setgid. Ito ay palaging isang magandang ideya para sa mga server o anumang programa na tumatakbo
sa ngalan ng ibang tao (tulad ng mga script ng CGI), dahil binabawasan nito ang mga pagkakataong makukuha ng mga tao
ang labas ay magagawang ikompromiso ang iyong sistema.
Tingnan natin ang isa pang TCP client. Kumokonekta ang isang ito sa serbisyong "oras" ng TCP sa isang numero
ng iba't ibang makina at nagpapakita kung gaano kalayo ang pagkakaiba ng kanilang mga orasan sa system kung saan ito kinaroroonan
pinapatakbo:
#!/usr/bin/perl -w
gumamit ng mahigpit;
gumamit ng Socket;
aking $SECS_OF_70_YEARS = 2208988800;
sub ctime { scalar localtime(shift() || time()) }
my $iaddr = gethostbyname("localhost");
my $proto = getprotobyname("tcp");
my $port = getservbyname("oras", "tcp");
my $paddr = sockaddr_in(0, $iaddr);
my($host);
$| = 1;
printf "%-24s %8s %s\n", "localhost", 0, ctime();
foreach $host (@ARGV) {
printf "%-24s", $host;
my $hisiaddr = inet_aton($host) || mamatay "hindi kilalang host";
my $hispaddr = sockaddr_in($port, $hisiaddr);
socket(SOCKET, PF_INET, SOCK_STREAM, $proto)
|| mamatay "socket: $!";
kumonekta(SOCKET, $hispaddr) || mamatay "kunekta: $!";
my $rtime = pack("C4", ());
basahin(SOCKET, $rtime, 4);
malapit (SOCKET);
my $histime = unpack("N", $rtime) - $SECS_OF_70_YEARS;
printf "%8d %s\n", $histime - time(), ctime($histime);
}
Unix-Domain TCP Kliyente at Server
Ayos lang iyon para sa mga kliyente at server ng Internet-domain, ngunit paano naman ang mga lokal na komunikasyon?
Bagama't maaari mong gamitin ang parehong setup, minsan ay ayaw mo. Ang mga socket ng Unix-domain ay
lokal sa kasalukuyang host, at kadalasang ginagamit sa loob upang ipatupad ang mga tubo. Unlike
Ang mga socket ng domain ng Internet, ang mga socket ng Unix na domain ay maaaring magpakita sa file system na may isang ls(1)
listahan.
% ls -l /dev/log
srw-rw-rw- 1 ugat 0 Okt 31 07:23 /dev/log
Maaari mong subukan ang mga ito gamit ang Perl's -S pagsubok ng file:
maliban kung (-S "/dev/log") {
mamatay "may masama sa sistema ng log";
}
Narito ang isang sample na Unix-domain client:
#!/usr/bin/perl -w
gumamit ng Socket;
gumamit ng mahigpit;
aking ($rendezvous, $line);
$rendezvous = shift || "catsock";
socket(SOCK, PF_UNIX, SOCK_STREAM, 0) || mamatay "socket: $!";
kumonekta(SOCK, sockaddr_un($rendezvous)) || mamatay "kunekta: $!";
while (defined ($line = )) {
i-print ang $line;
}
lumabas(0);
At narito ang isang kaukulang server. Hindi mo kailangang mag-alala tungkol sa hangal na network
terminator dito dahil ang mga socket ng Unix domain ay garantisadong nasa localhost, at
kaya lahat ay gumagana nang tama.
#!/usr/bin/perl -Tw
gumamit ng mahigpit;
gumamit ng Socket;
gumamit ng Carp;
SIMULAN { $ENV{PATH} = "/ usr / bin:/ bin"}
sub spawn; # pasulong na deklarasyon
sub logmsg { print "$0 $$: @_ at ", scalar localtime(), "\n" }
aking $NAME = "catsock";
my $uaddr = sockaddr_un($NAME);
my $proto = getprotobyname("tcp");
socket(Server, PF_UNIX, SOCK_STREAM, 0) || mamatay "socket: $!";
i-unlink($NAME);
bind (Server, $uaddr) || mamatay "bind: $!";
makinig(Server, SOMAXCONN) || mamatay "makinig: $!";
logmsg "nagsimula ang server sa $NAME";
aking $waitedpid;
gamitin ang POSIX ":sys_wait_h";
sub REAPER {
aking $anak;
habang (($waitedpid = waitpid(-1, WNOHANG)) > 0) {
logmsg "inani $waitedpid" . ($? ? " na may exit $?" : "");
}
$SIG{CHLD} = \&REAPER; # kinasusuklaman ang SysV
}
$SIG{CHLD} = \&REAPER;
para sa ( $waitedpid = 0;
tanggapin(Client, Server) || $waitedpid;
$waitedpid = 0, malapit na Kliyente)
{
susunod kung $waitedpid;
logmsg "koneksyon sa $NAME";
spawn sub {
print "Hello there, it's now ", scalar localtime(), "\n";
exec("/usr/games/fortune") || mamatay "hindi makapagpatupad ng kapalaran: $!";
};
}
sub spawn {
aking $coderef = shift();
maliban kung (@_ == 0 && $coderef && ref($coderef) eq "CODE") {
aminin ang "paggamit: spawn CODEREF";
}
aking $pid;
maliban kung (defined ($pid = fork())) {
logmsg "hindi ma-fork: $!";
bumalik;
}
elsif ($pid) {
logmsg "begat $pid";
bumalik; # Ako ang magulang
}
tao {
# Ako ang bata -- go spawn
}
bukas(STDIN, "<&Client") || mamatay "hindi maaaring dup client sa stdin";
open(STDOUT, ">&Client") || mamatay "hindi maaaring dup client sa stdout";
## bukas(STDERR, ">&STDOUT") || mamatay "hindi maaaring dup stdout sa stderr";
exit($coderef->());
}
Tulad ng nakikita mo, ito ay kahanga-hangang katulad sa TCP server ng domain ng Internet, sa gayon, sa
katotohanan, na inalis namin ang ilang mga duplicate na function--spawn(), logmsg(), ctime(), at
REAPER()--na kapareho ng sa ibang server.
Kaya bakit mo gugustuhin na gumamit ng Unix domain socket sa halip na isang mas simpleng pinangalanang pipe?
Dahil ang isang pinangalanang pipe ay hindi nagbibigay sa iyo ng mga session. Hindi mo masasabi ang data ng isang proseso mula sa
ng iba. Sa socket programming, makakakuha ka ng hiwalay na session para sa bawat kliyente; iyon ay
bakit tanggapin () tumatagal ng dalawang argumento.
Halimbawa, sabihin nating mayroon kang matagal nang tumatakbong database server daemon na gusto mo
mga tao upang ma-access mula sa Web, ngunit kung sila ay dumaan sa isang CGI interface.
Magkakaroon ka ng maliit, simpleng CGI program na gumagawa ng anumang mga pagsusuri at pag-log na nararamdaman mo
tulad ng, at pagkatapos ay gumaganap bilang isang Unix-domain client at kumokonekta sa iyong pribadong server.
TCP Kliyente sa IO::Socket
Para sa mga mas gusto ang isang mas mataas na antas ng interface kaysa sa socket programming, ang IO::Socket module
nagbibigay ng isang object-oriented na diskarte. Kung sa ilang kadahilanan ay kulang ka sa modyul na ito, magagawa mo
kunin lang ang IO::Socket mula sa CPAN, kung saan makakahanap ka rin ng mga module na nagbibigay ng mga madaling interface
sa mga sumusunod na system: DNS, FTP, Ident (RFC 931), NIS at NISPlus, NNTP, Ping, POP3,
SMTP, SNMP, SSLeay, Telnet, at Oras--upang pangalanan lamang ang ilan.
A Simple Kliente
Narito ang isang kliyente na gumagawa ng koneksyon sa TCP sa serbisyong "araw" sa port 13 ng
host name na "localhost" at nagpi-print ng lahat ng bagay na mahalaga na ibigay ng server doon.
#!/usr/bin/perl -w
gumamit ng IO::Socket;
$remote = IO::Socket::INET->bago(
Proto => "tcp",
PeerAddr => "localhost",
PeerPort => "araw(13)",
)
|| mamatay "hindi makakonekta sa pang-araw na serbisyo sa localhost";
habang (<$remote>) { print }
Kapag pinatakbo mo ang program na ito, dapat kang makakuha ng isang bagay na mukhang ganito:
Miyer Mayo 14 08:40:46 MDT 1997
Narito kung ano ang mga parameter na iyon sa bago() ibig sabihin ng tagabuo:
"Proto"
Ito ang protocol na gagamitin. Sa kasong ito, ang ibinalik na socket handle ay magiging
konektado sa isang TCP socket, dahil gusto namin ng stream-oriented na koneksyon, iyon ay, isa
na gumaganap na halos tulad ng isang simpleng lumang file. Hindi lahat ng socket ay ganito ang uri.
Halimbawa, ang UDP protocol ay maaaring gamitin para gumawa ng datagram socket, na ginagamit para sa mensahe-
dumaraan.
"PeerAddr"
Ito ang pangalan o Internet address ng remote host kung saan tumatakbo ang server. Kami
maaaring tumukoy ng mas mahabang pangalan tulad ng "www.perl.com", o isang address na tulad ng
"207.171.7.72". Para sa mga layunin ng pagpapakita, ginamit namin ang espesyal na hostname
"localhost", na dapat palaging nangangahulugang ang kasalukuyang machine na iyong pinapatakbo. Ang
Ang kaukulang Internet address para sa localhost ay "127.0.0.1", kung mas gusto mong gamitin iyon.
"PeerPort"
Ito ang pangalan ng serbisyo o numero ng port na gusto naming kumonekta. Nakuha sana namin
malayo sa paggamit lamang ng "araw" sa mga system na may mahusay na na-configure na mga serbisyo ng system
file,[FOOTNOTE: Ang system services file ay matatagpuan sa / etc / services sa ilalim ng Unixy
system.] ngunit dito namin tinukoy ang port number (13) sa mga panaklong. Gamit lang
ang numero ay gagana rin, ngunit ang mga literal na numero ay gumagawa ng maingat na mga programmer
kinakabahan.
Pansinin kung paano ginagamit ang return value mula sa "bagong" constructor bilang filehandle sa
"habang" loop? Yan ang tinatawag na an hindi tuwiran filehandle, isang scalar variable na naglalaman ng a
filehandle. Magagamit mo ito sa parehong paraan na gagawin mo sa isang normal na filehandle. Halimbawa, ikaw
maaaring basahin ang isang linya mula dito sa ganitong paraan:
$line = <$handle>;
lahat ng natitirang linya mula sa ay ganito:
@lines = <$handle>;
at magpadala ng linya ng data dito sa ganitong paraan:
i-print ang $handle "ilang data\n";
A Webget Kliente
Narito ang isang simpleng kliyente na kumukuha ng isang malayuang host para kumuha ng dokumento, at pagkatapos ay isang listahan
ng mga file na makukuha mula sa host na iyon. Ito ay isang mas kawili-wiling kliyente kaysa sa nauna
dahil nagpapadala muna ito ng isang bagay sa server bago kunin ang tugon ng server.
#!/usr/bin/perl -w
gumamit ng IO::Socket;
maliban kung (@ARGV > 1) { mamatay "gamit: $0 host url ..." }
$host = shift(@ARGV);
$EOL = "\015\012";
$BLANK = $EOL x 2;
para sa aking $document (@ARGV) {
$remote = IO::Socket::INET->new( Proto => "tcp",
PeerAddr => $host,
PeerPort => "http(80)",
) || mamatay "hindi makakonekta sa httpd sa $host";
$remote->autoflush(1);
print $remote "GET $document HTTP/1.0" . $BLANK;
habang ( <$remote> ) { print }
isara ang $remote;
}
Ang web server na humahawak sa serbisyo ng HTTP ay ipinapalagay na nasa karaniwang port nito, numero 80.
Kung ang server na sinusubukan mong kumonekta ay nasa ibang port, tulad ng 1080 o 8080,
dapat itong tukuyin bilang ang pinangalanang-parameter na pares, "PeerPort => 8080". Ang "autoflush" na paraan
ay ginagamit sa socket dahil kung hindi ay buffer up ng system ang output na ipinadala namin nito.
(Kung nasa isang prehistoric Mac ka, kakailanganin mo ring baguhin ang bawat "\n" sa iyong code na
nagpapadala ng data sa network upang maging "\015\012" sa halip.)
Ang pagkonekta sa server ay ang unang bahagi lamang ng proseso: kapag mayroon ka na
koneksyon, kailangan mong gamitin ang wika ng server. Ang bawat server sa network ay may kanya-kanyang sarili
maliit na command language na inaasahan nito bilang input. Ang string na ipinapadala namin sa server
nagsisimula sa "GET" ay nasa HTTP syntax. Sa kasong ito, hinihiling lang namin ang bawat tinukoy
dokumento. Oo, gumagawa talaga kami ng bagong koneksyon para sa bawat dokumento, kahit na
ang parehong host. Iyan ang paraang palagi mong kailangang magsalita ng HTTP. Mga kamakailang bersyon ng
maaaring hilingin ng mga web browser na iwanan ng malayong server na bukas ang koneksyon nang ilang sandali,
ngunit hindi kailangang igalang ng server ang ganoong kahilingan.
Narito ang isang halimbawa ng pagpapatakbo ng program na iyon, na tatawagin namin webget:
% webget www.perl.com /guanaco.html
Hindi Natagpuan ang HTTP/1.1 404 File
Petsa: Huwebes, 08 Mayo 1997 18:02:32 GMT
Server: Apache/1.2b6
Koneksyon: malapit
Uri ng nilalaman: text/html
Hindi Natagpuan ang 404 File
Hindi Nahanap ang File
Ang hiniling na URL /guanaco.html ay hindi nakita sa server na ito.
Ok, kaya hindi iyon masyadong kawili-wili, dahil hindi nito nakita ang partikular na dokumentong iyon. Pero
isang mahabang tugon ay hindi magkasya sa pahinang ito.
Para sa isang mas tampok na bersyon ng program na ito, dapat kang tumingin sa lwp-request programa
kasama sa mga LWP modules mula sa CPAN.
Interactive Kliente sa IO::Socket
Well, ayos lang iyon kung gusto mong magpadala ng isang utos at makakuha ng isang sagot, ngunit paano naman
pagse-set up ng isang bagay na ganap na interactive, medyo katulad ng paraan telnet gumagana? Sa ganoong paraan ikaw
maaaring mag-type ng linya, makuha ang sagot, mag-type ng linya, makuha ang sagot, atbp.
Ang kliyenteng ito ay mas kumplikado kaysa sa dalawang nagawa namin sa ngayon, ngunit kung ikaw ay nasa isang sistema
na sumusuporta sa malakas na "tinidor" na tawag, ang solusyon ay hindi ganoon kagaspang. Kapag nagawa mo na
ang koneksyon sa anumang serbisyo na gusto mong maka-chat, tumawag sa "fork" para i-clone ang iyong
proseso. Ang bawat isa sa dalawang magkaparehong prosesong ito ay may napakasimpleng trabahong dapat gawin: ang magulang
kinokopya ang lahat mula sa socket hanggang sa karaniwang output, habang ang bata ay sabay-sabay
kinokopya ang lahat mula sa karaniwang input hanggang sa socket. Upang magawa ang parehong bagay gamit
isang proseso lang sana magkano mas mahirap, dahil mas madaling mag-code ng dalawang proseso para magawa ang isa
bagay kaysa sa pag-code ng isang proseso upang magawa ang dalawang bagay. (Itong keep-it-simpleng prinsipyo a
pundasyon ng pilosopiya ng Unix, at mahusay na software engineering pati na rin, na
marahil kung bakit ito kumalat sa ibang mga sistema.)
Narito ang code:
#!/usr/bin/perl -w
gumamit ng mahigpit;
gumamit ng IO::Socket;
aking ($host, $port, $kidpid, $handle, $line);
maliban kung (@ARGV == 2) { mamatay "gamit: $0 host port" }
($host, $port) = @ARGV;
# lumikha ng tcp na koneksyon sa tinukoy na host at port
$handle = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $port)
|| mamatay "hindi makakonekta sa port $port sa $host: $!";
$hawakan->autoflush(1); # kaya nakarating kaagad ang output
print STDERR "[Nakakonekta sa $host:$port]\n";
# hatiin ang programa sa dalawang proseso, identical twins
mamatay "hindi maaaring tinidor: $!" maliban kung tinukoy ($ kidpid = tinidor ());
# ang if{} block ay tumatakbo lamang sa proseso ng magulang
kung ($kidpid) {
# kopyahin ang socket sa karaniwang output
habang (tinukoy ($line = <$handle>)) {
i-print ang STDOUT $line;
}
kill("TERM", $kidpid); # magpadala ng SIGTERM sa bata
}
# ang ibang{} block ay tumatakbo lamang sa proseso ng bata
tao {
# kopyahin ang karaniwang input sa socket
habang (tinukoy ($line = )) {
i-print ang $handle $line;
}
lumabas(0); # kung sakali
}
Ang function na "kill" sa block na "if" ng magulang ay nandoon para magpadala ng signal sa ating anak
proseso, kasalukuyang tumatakbo sa "iba" na bloke, sa sandaling magsara ang remote server
pagtatapos nito sa koneksyon.
Kung ang remote server ay nagpapadala ng data ng isang byte sa oras, at kailangan mo ang data na iyon kaagad nang wala
naghihintay para sa isang bagong linya (na maaaring hindi mangyari), maaaring naisin mong palitan ang "habang" loop
sa magulang na may sumusunod:
aking $byte;
habang (sysread($handle, $byte, 1) == 1) {
i-print ang STDOUT $byte;
}
Ang paggawa ng system call para sa bawat byte na gusto mong basahin ay hindi masyadong episyente (para sabihin
mahinahon) ngunit ito ang pinakasimpleng ipaliwanag at gumagana nang maayos.
TCP Server sa IO::Socket
Gaya ng nakasanayan, ang pagse-set up ng isang server ay mas kasangkot sa pagpapatakbo ng isang kliyente. Ang
modelo ay na ang server ay lumilikha ng isang espesyal na uri ng socket na walang ginawa kundi makinig
isang partikular na port para sa mga papasok na koneksyon. Ginagawa ito sa pamamagitan ng pagtawag sa
Paraang "IO::Socket::INET->new()" na may bahagyang naiibang argumento kaysa ginawa ng kliyente.
Iyon ang dahilan kung bakit
Ito ang protocol na gagamitin. Tulad ng aming mga kliyente, tutukuyin pa rin namin ang "tcp" dito.
LocalPort
Tinukoy namin ang isang lokal na port sa argumentong "LocalPort", na hindi namin ginawa para sa
kliyente. Ito ang pangalan ng serbisyo o numero ng port kung saan gusto mong maging server.
(Sa ilalim ng Unix, ang mga port sa ilalim ng 1024 ay limitado sa superuser.) Sa aming sample, kami ay
gumamit ng port 9000, ngunit maaari mong gamitin ang anumang port na kasalukuyang hindi ginagamit sa iyong system.
Kung susubukan mong gamitin ang isa na ginagamit na, makakatanggap ka ng mensaheng "Ginagamit na ang address."
Sa ilalim ng Unix, ipapakita ng command na "netstat -a" kung aling mga serbisyo ang kasalukuyang may mga server.
Bar
Ang parameter na "Makinig" ay nakatakda sa maximum na bilang ng mga nakabinbing koneksyon na magagawa namin
tanggapin hanggang talikuran namin ang mga papasok na kliyente. Isipin ito bilang isang queue na naghihintay ng tawag para sa
iyong telepono. Ang low-level na Socket module ay may espesyal na simbolo para sa system
maximum, na SOMAXCONN.
Muling gumamit
Ang parameter na "Muling gamitin" ay kailangan upang i-restart namin nang manu-mano ang aming server nang hindi naghihintay
ilang minuto upang payagan ang mga buffer ng system na maalis.
Kapag nagawa na ang generic na socket ng server gamit ang mga parameter na nakalista sa itaas, ang
pagkatapos ay naghihintay ang server para sa isang bagong kliyente na kumonekta dito. Ang server ay humaharang sa "tanggapin"
paraan, na kalaunan ay tumatanggap ng bidirectional na koneksyon mula sa malayong kliyente. (Gawin
tiyaking i-autoflush ang handle na ito upang iwasan ang pag-buffer.)
Upang idagdag sa pagiging kabaitan ng gumagamit, sinenyasan ng aming server ang gumagamit para sa mga utos. Karamihan sa mga server ay hindi
gawin ito. Dahil sa prompt na walang bagong linya, kakailanganin mong gamitin ang "sysread"
variant ng interactive na kliyente sa itaas.
Ang server na ito ay tumatanggap ng isa sa limang magkakaibang mga utos, na nagpapadala ng output pabalik sa kliyente.
Hindi tulad ng karamihan sa mga server ng network, ang isang ito ay humahawak lamang ng isang papasok na kliyente sa isang pagkakataon.
Ang mga multitasking server ay saklaw sa Kabanata 16 ng Camel.
Narito ang code. gagawin namin
#!/usr/bin/perl -w
gumamit ng IO::Socket;
gumamit ng Net::hostent; # para sa OOish na bersyon ng gethostbyaddr
$PORT = 9000; # pumili ng hindi ginagamit
$server = IO::Socket::INET->new( Proto => "tcp",
LocalPort => $PORT,
Makinig => SOMAXCONN,
Muling gamitin => 1);
mamatay "hindi makapag-setup ng server" maliban kung $server;
print "[Server $0 tumatanggap ng mga kliyente]\n";
habang ($client = $server->accept()) {
$client->autoflush(1);
print $client "Welcome to $0; type help for command list.\n";
$hostinfo = gethostbyaddr($client->peeraddr);
printf "[Kumonekta mula sa %s]\n", $hostinfo ? $hostinfo->name : $client->peerhost;
print $client "Utos?";
habang ( <$client>) {
susunod maliban kung /\S/; # blankong linya
kung (/quit|exit/i) { last }
elsif (/petsa|oras/i) { printf $client "%s\n", scalar localtime() }
elsif (/who/i ) { print $client `who 2>&1` }
elsif (/cookie/i ) { print $client `/usr/games/fortune 2>&1` }
elsif (/motd/i ) { print $client `cat /etc/motd 2>&1` }
tao {
print $client "Mga utos: quit date who cookie motd\n";
}
} magpatuloy {
print $client "Utos?";
}
isara ang $client;
}
PDU: mensahe Pagdaan
Ang isa pang uri ng pag-setup ng client-server ay isa na hindi gumagamit ng mga koneksyon, ngunit mga mensahe. UDP
ang mga komunikasyon ay nagsasangkot ng mas mababang overhead ngunit nagbibigay din ng hindi gaanong pagiging maaasahan, tulad ng mayroon
walang mga pangako na ang mga mensahe ay darating sa lahat, pabayaan mag-isa sa ayos at unmalled. Pa rin,
Nag-aalok ang UDP ng ilang mga pakinabang sa TCP, kabilang ang kakayahang "mag-broadcast" o "multicast" sa
isang buong grupo ng mga destination host nang sabay-sabay (karaniwan ay sa iyong lokal na subnet). Kung mahanap mo
ang iyong sarili ay labis na nag-aalala tungkol sa pagiging maaasahan at simulan ang pagbuo ng mga pagsusuri sa iyong mensahe
system, kung gayon marahil ay dapat mong gamitin lamang ang TCP upang magsimula.
Ang mga datagram ng UDP ay hindi isang bytestream at hindi dapat ituring na ganoon. Ginagawa nitong gamit
Mga mekanismo ng I/O na may panloob na buffering tulad ng stdio (hal i-print () at mga kaibigan) lalo na
nakakapagod. Gamitin syswrite(), o mas mabuti ipadala (), tulad ng sa halimbawa sa ibaba.
Narito ang isang UDP program na katulad ng halimbawang Internet TCP client na ibinigay kanina. gayunpaman,
sa halip na suriin ang isang host sa isang pagkakataon, ang bersyon ng UDP ay susuriin ang marami sa kanila
asynchronously sa pamamagitan ng pagtulad sa isang multicast at pagkatapos ay gamit piliin() para gumawa ng time-out na paghihintay
para sa I/O. Upang makagawa ng katulad sa TCP, kailangan mong gumamit ng ibang socket handle
para sa bawat host.
#!/usr/bin/perl -w
gumamit ng mahigpit;
gumamit ng Socket;
gamitin ang Sys::Hostname;
my ($count, $hisiaddr, $hispaddr, $histime,
$host, $iaddr, $paddr, $port, $proto,
$rin, $rout, $rtime, $SECS_OF_70_YEARS);
$SECS_OF_70_YEARS = 2_208_988_800;
$iaddr = gethostbyname(hostname());
$proto = getprotobyname("udp");
$port = getservbyname("oras", "udp");
$paddr = sockaddr_in(0, $iaddr); Ang ibig sabihin ng # 0 ay hayaan ang kernel na pumili
socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) || mamatay "socket: $!";
bind(SOCKET, $paddr) || mamatay "bind: $!";
$| = 1;
printf "%-12s %8s %s\n", "localhost", 0, scalar localtime();
$ Count = 0;
para sa $host (@ARGV) {
$count++;
$hisiaddr = inet_aton($host) || mamatay "hindi kilalang host";
$hispaddr = sockaddr_in($port, $hisiaddr);
tinukoy(send(SOCKET, 0, 0, $hispaddr)) || mamatay "send $host: $!";
}
$rin = "";
vec($rin, fileno(SOCKET), 1) = 1;
# timeout pagkatapos ng 10.0 segundo
habang ($count && select($rout = $rin, undef, undef, 10.0)) {
$rtime = "";
$hispaddr = recv(SOCKET, $rtime, 4, 0) || mamatay "recv: $!";
($port, $hisiaddr) = sockaddr_in($hispaddr);
$host = gethostbyaddr($hisiaddr, AF_INET);
$histime = unpack("N", $rtime) - $SECS_OF_70_YEARS;
printf "%-12s", $host;
printf "%8d %s\n", $histime - time(), scalar localtime($histime);
$bilang--;
}
Ang halimbawang ito ay hindi kasama ang anumang mga muling pagsubok at maaaring dahil dito ay mabigong makipag-ugnayan sa isang naaabot
host. Ang pinakakilalang dahilan nito ay ang pagsisikip ng mga pila sa nagpapadalang host
kung ang bilang ng mga host na makontak ay sapat na malaki.
sysv IPC
Bagama't ang System V IPC ay hindi gaanong ginagamit bilang mga socket, mayroon pa rin itong ilang kawili-wiling gamit.
Gayunpaman, hindi mo magagamit ang SysV IPC o Berkeley mmap() upang magkaroon ng isang variable na ibinahagi sa gitna
ilang mga proseso. Iyon ay dahil muling itatalaga ni Perl ang iyong string kapag wala ka
gusto nito. Maaari mong tingnan ang "IPC::Shareable" o "threads::shared" na mga module para sa
na.
Narito ang isang maliit na halimbawa na nagpapakita ng nakabahaging paggamit ng memorya.
gamitin ang IPC::SysV qw(IPC_PRIVATE IPC_RMID S_IRUSR S_IWUSR);
$laki = 2000;
$id = shmget(IPC_PRIVATE, $size, S_IRUSR | S_IWUSR);
tinukoy($id) || mamatay "shmget: $!";
i-print ang "shm key $id\n";
$message = "Mensahe #1";
shmwrite($id, $message, 0, 60) || mamatay "shmwrite: $!";
print "nagsulat: '$message'\n";
shmread($id, $buff, 0, 60) || mamatay "shmread: $!";
print "read : '$buff'\n";
# ang buffer ng shmread ay zero-character na end-padded.
substr($buff, index($buff, "\0")) = "";
i-print ang "un" maliban kung $buff eq $message;
i-print ang "bukol\n";
i-print ang "pagtanggal ng shm $id\n";
shmctl($id, IPC_RMID, 0) || mamatay "shmctl: $!";
Narito ang isang halimbawa ng isang semaphore:
gumamit ng IPC::SysV qw(IPC_CREAT);
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 10, 0666 | IPC_CREAT);
tinukoy($id) || mamatay "semget: $!";
i-print ang "sem id $id\n";
Ilagay ang code na ito sa isang hiwalay na file na tatakbo sa higit sa isang proseso. Tawagan ang file kumuha:
# lumikha ng isang semaphore
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0, 0);
tinukoy($id) || mamatay "semget: $!";
$semnum = 0;
$semflag = 0;
# "kumuha" ng semaphore
# hintayin ang semaphore na maging zero
$semop = 0;
$opstring1 = pack("s!s!s!", $semnum, $semop, $semflag);
# Dagdagan ang bilang ng semaphore
$semop = 1;
$opstring2 = pack("s!s!s!", $semnum, $semop, $semflag);
$opstring = $opstring1 . $opstring2;
semop($id, $opstring) || mamatay "semop: $!";
Ilagay ang code na ito sa isang hiwalay na file na tatakbo sa higit sa isang proseso. Tawagan ang file na ito magbigay:
# "ibigay" ang semaphore
# patakbuhin ito sa orihinal na proseso at makikita mo
# na magpapatuloy ang pangalawang proseso
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0, 0);
mamatay maliban kung tinukoy($id);
$semnum = 0;
$semflag = 0;
# Bawasan ang bilang ng semaphore
$semop = -1;
$opstring = pack("s!s!s!", $semnum, $semop, $semflag);
semop($id, $opstring) || mamatay "semop: $!";
Ang SysV IPC code sa itaas ay isinulat na matagal na ang nakalipas, at tiyak na clunky ang hitsura nito. Para sa
mas modernong hitsura, tingnan ang IPC::SysV module.
Isang maliit na halimbawa na nagpapakita ng SysV message queue:
gamitin ang IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_CREAT S_IRUSR S_IWUSR);
my $id = msgget(IPC_PRIVATE, IPC_CREAT | S_IRUSR | S_IWUSR);
tinukoy($id) || mamatay "msgget failed: $!";
my $sent = "mensahe";
ang aking $type_sent = 1234;
msgsnd($id, pack("l! a*", $type_sent, $sent), 0)
|| mamatay "msgsnd failed: $!";
msgrcv($id, my $rcvd_buf, 60, 0, 0)
|| mamatay "msgrcv failed: $!";
my($type_rcvd, $rcvd) = unpack("l! a*", $rcvd_buf);
kung ($rcvd eq $ipadala) {
i-print ang "okay\n";
} Iba pa {
print "hindi okay\n";
}
msgctl($id, IPC_RMID, 0) || mamatay "msgctl failed: $!\n";
NOTA
Karamihan sa mga gawaing ito ay tahimik ngunit magalang na nagbabalik ng "undef" kapag nabigo sila sa halip na
nagiging sanhi ng pagkamatay ng iyong programa kaagad at doon dahil sa isang hindi nahuhuling pagbubukod. (Sa totoo lang,
ilan sa mga bago Saksakan ginagawa ng mga function ng conversion croak() sa masasamang argumento.) Kaya naman
mahalaga upang suriin ang mga halaga ng pagbabalik mula sa mga function na ito. Palaging simulan ang iyong mga socket program
sa paraang ito para sa pinakamainam na tagumpay, at huwag kalimutang idagdag ang -T taint-checking flag sa
"#!" linya para sa mga server:
#!/usr/bin/perl -Tw
gumamit ng mahigpit;
gumamit ng sigtrap;
gumamit ng Socket;
Gamitin ang perlipc online gamit ang mga serbisyo ng onworks.net