Dit is de opdracht perlipc die kan worden uitgevoerd in de gratis hostingprovider van OnWorks met behulp van een van onze meerdere gratis online werkstations zoals Ubuntu Online, Fedora Online, Windows online emulator of MAC OS online emulator
PROGRAMMA:
NAAM
perlipc - Perl-communicatie tussen processen (signalen, fifo's, pijpen, veilige subprocessen,
stopcontacten en seinpalen)
PRODUCTBESCHRIJVING
De basis IPC-faciliteiten van Perl zijn opgebouwd uit de goede oude Unix-signalen, genaamd pipelines,
pipe wordt geopend, de Berkeley socket-routines en SysV IPC-oproepen. Elk wordt een beetje gebruikt
verschillende situaties.
Signalen
Perl gebruikt een eenvoudig signaalverwerkingsmodel: de %SIG-hash bevat namen of referenties van
door de gebruiker geïnstalleerde signaalbehandelaars. Deze handlers worden aangeroepen met een argument dat is
de naam van het signaal dat het heeft geactiveerd. Er kan opzettelijk een signaal worden gegenereerd uit een
een bepaalde toetsenbordvolgorde zoals controle-C of controle-Z, die door een ander naar u is verzonden
proces, of automatisch geactiveerd door de kernel wanneer er speciale gebeurtenissen plaatsvinden, zoals a
het onderliggende proces wordt afgesloten, uw eigen proces heeft onvoldoende stapelruimte of raakt een proces
limiet voor bestandsgrootte.
Om bijvoorbeeld een interruptsignaal op te vangen, stelt u een handler als volgt in:
onze $kafjes;
sub catch_zap {
mijn $naam = ploeg;
$schijt++;
die "Iemand heeft mij een SIG$-naam gestuurd";
}
$SIG{INT} = __PAKKET__ . "::catch_zap";
$SIG{INT} = \&catch_zap; # beste strategie
Vóór Perl 5.8.0 was het nodig om zo min mogelijk te doen in uw computer
begeleider; Merk op dat we alleen maar een globale variabele instellen en vervolgens een uitzondering maken.
Dat komt omdat op de meeste systemen bibliotheken niet opnieuw binnenkomen; vooral geheugen
toewijzings- en I/O-routines niet. Dat betekende dat het bijna gedaan was iets in
handler zou in theorie een geheugenfout en daaropvolgende kerndump kunnen veroorzaken - zie "Uitgesteld
Signalen (veilige signalen)" hieronder.
De namen van de signalen zijn de namen die op uw systeem worden vermeld onder "kill -l", of u kunt dat ook doen
haal ze op met behulp van de CPAN-module IPC::Signal.
U kunt er ook voor kiezen om de strings "IGNORE" of "DEFAULT" als handler toe te wijzen, waarbij
In dat geval zal Perl proberen het signaal te negeren of het standaardding te doen.
Op de meeste Unix-platforms heeft het signaal "CHLD" (ook wel bekend als "CLD") een speciaal signaal
gedrag met betrekking tot de waarde "IGNORE". Zet $SIG{CHLD} op "IGNORE" op zo'n
platform heeft tot gevolg dat er geen zombieprocessen worden gecreëerd wanneer het bovenliggende proces er niet in slaagt
"wait()" op de onderliggende processen (dat wil zeggen, onderliggende processen worden automatisch geoogst). Roeping
"wait()" met $SIG{CHLD} ingesteld op "IGNORE" retourneert gewoonlijk "-1" op dergelijke platforms.
Sommige signalen kunnen niet worden opgevangen of genegeerd, zoals de KILL en STOP (maar niet de
TSTP)-signalen. Houd er rekening mee dat het negeren van signalen ervoor zorgt dat ze verdwijnen. Als je ze maar wilt
tijdelijk geblokkeerd zonder dat ze verloren gaan, moet u het sigprocmask van POSIX gebruiken.
Het verzenden van een signaal naar een negatief proces-ID betekent dat u het signaal naar het geheel verzendt
Unix-procesgroep. Deze code stuurt een ophangsignaal naar alle processen in de huidige
process groep, en stelt ook $SIG{HUP} in op "IGNORE" zodat het zichzelf niet doodt:
# blokbereik voor lokaal
{
lokaal $SIG{HUP} = "NEGEREN";
HUP doden => -$$;
# hip schrijven van: kill("HUP", -$$)
}
Een ander interessant signaal om te verzenden is signaal nummer nul. Dit heeft feitelijk geen invloed op a
kindproces, maar controleert in plaats daarvan of het leeft of de UID's heeft gewijzigd.
tenzij (dood 0 => $kid_pid) {
waarschuwen "er is iets vreselijks gebeurd met $kid_pid";
}
Signaalnummer nul kan mislukken omdat u geen toestemming heeft om het signaal te verzenden wanneer dit wordt aangegeven
bij een proces waarvan de echte of opgeslagen UID niet identiek is aan de echte of effectieve UID van de
verzendproces, ook al leeft het proces. Mogelijk kunt u de oorzaak achterhalen
van falen met behulp van $! of "%!".
tenzij (kill(0 => $pid) || $!{EPERM}) {
waarschuwen "$pid ziet er dood uit";
}
Mogelijk wilt u ook anonieme functies gebruiken voor eenvoudige signaalbehandelaars:
$SIG{INT} = sub { die "\nWeg hier!\n" };
SIGCHLD-handlers vereisen enige speciale zorg. Als een tweede kind sterft terwijl hij in het signaal is
handler veroorzaakt door de eerste dode, krijgen we geen signaal meer. Dus moeten we hier een lus maken
zal het niet-geoogste kind als een zombie achterlaten. En de volgende keer dat er twee kinderen sterven, krijgen we
nog een zombie. Enzovoort.
gebruik POSIX ":sys_wait_h";
$SIG{CHLD} = sub {
while ((mijn $child = waitpid(-1, WNOHANG)) > 0) {
$Kid_Status{$kind} = $?;
}
};
#doe iets dat vorkt...
Wees voorzichtig: qx(), systeem(), en sommige modules voor het aanroepen van externe opdrachten doen a vork(),
harte wacht() voor het resultaat. Uw signaalbehandelaar wordt dus gebeld. Omdat wacht() was
al langsgebeld systeem() or qx() wacht() in de signaalbehandelaar zal niets meer zien
zombies en zal daarom blokkeren.
De beste manier om dit probleem te voorkomen is door gebruik te maken van wachtend(), zoals in het volgende voorbeeld:
gebruik POSIX ":sys_wait_h"; # voor niet-blokkerend lezen
mijn %kinderen;
$SIG{CHLD} = sub {
# verander $ niet! en $? externe begeleider
lokaal ($!, $?);
while ( (mijn $pid = waitpid(-1, WNOHANG)) > 0 ) {
verwijder $children{$pid};
cleanup_child($pid, $?);
}
};
terwijl (1) {
mijn $pid = vork();
die "kan niet splitsen" tenzij gedefinieerd $pid;
als ($pid == 0) {
#...
uitgang 0;
} Else {
$kinderen{$pid}=1;
#...
systeem($commando);
#...
}
}
Signaalverwerking wordt ook gebruikt voor time-outs in Unix. Terwijl veilig beschermd binnen een
"eval{}"-blok, stelt u een signaalhandler in om alarmsignalen op te vangen en vervolgens te plannen
één die binnen een aantal seconden bij u wordt afgeleverd. Probeer dan uw blokkeeroperatie,
het alarm wissen wanneer het klaar is, maar niet voordat u uw "eval{}"-blok heeft verlaten. Als het
gaat uit, je zult gebruiken sterven () uit het blok te springen.
Hier is een voorbeeld:
mijn $ALARM_EXCEPTION = "wekker opnieuw opstarten";
evalueren {
lokaal $SIG{ALRM} = sub { die $ALARM_EXCEPTION };
alarm 10;
kudde(FH, 2) # schrijfvergrendeling blokkeren
|| sterven "kan niet massaal: $!";
alarm 0;
};
if ($@ && $@ !~ quotemeta($ALARM_EXCEPTION)) { die }
Als er een time-out optreedt voor de bewerking systeem() or qx(), deze techniek kan genereren
zombies. Als dit voor u belangrijk is, moet u dit zelf doen vork() en exec (), en doden
het dwalende kindproces.
Voor complexere signaalverwerking ziet u mogelijk de standaard POSIX-module. Helaas,
dit is bijna geheel ongedocumenteerd, maar de t/lib/posix.t bestand uit de Perl-bron
distributie bevat enkele voorbeelden.
Behandeling the ZUCHT Signaal in daemons
Een proces dat gewoonlijk start wanneer het systeem opstart en wordt afgesloten wanneer het systeem wordt afgesloten
down wordt een daemon genoemd (Disk And Execution MONitor). Als een daemonproces een
configuratiebestand dat wordt gewijzigd nadat het proces is gestart, moet er een
manier om dat proces te vertellen het configuratiebestand opnieuw te lezen zonder het proces te stoppen.
Veel daemons bieden dit mechanisme aan met behulp van een "SIGHUP"-signaalhandler. Wanneer je het wilt vertellen
de daemon om het bestand opnieuw te lezen, stuurt u eenvoudigweg het "SIGHUP"-signaal.
In het volgende voorbeeld wordt een eenvoudige daemon geïmplementeerd, die zichzelf telkens opnieuw opstart als de
Het "SIGHUP"-signaal wordt ontvangen. De daadwerkelijke code bevindt zich in de subroutine "code()", die
drukt gewoon wat foutopsporingsinformatie af om te laten zien dat het werkt; het moet worden vervangen door de echte
code.
#!/usr/bin/perl
gebruik strikt;
gebruik waarschuwingen;
gebruik POSIX ();
gebruik FindBin ();
gebruik Bestand::Basisnaam ();
gebruik File::Spec::Functions qw(catfile);
$| = 1;
# maak de daemon platformonafhankelijk, zodat exec altijd het script aanroept
# zichzelf met het juiste pad, ongeacht hoe het script werd aangeroepen.
mijn $script = Bestand::Basenaam::basisnaam($0);
mijn $SELF = catfile($FindBin::Bin, $script);
# POSIX ontmaskert het sigprocmasker correct
$SIG{HUP} = sub{
print "kreeg SIGHUP\n";
exec($SELF, @ARGV) || die "$0: kon niet opnieuw opstarten: $!";
};
code();
subcode {
print "PID: $$\n";
print "ARGV: @ARGV\n";
mijn $count = 0;
terwijl (1) {
slaap 2;
print ++$count, "\n";
}
}
Uitgestelde Signalen (Veilig Signalen)
Vóór Perl 5.8.0 werd u bij het installeren van Perl-code om met signalen om te gaan blootgesteld aan gevaar
twee dingen. Ten eerste zijn er maar weinig systeembibliotheekfuncties die opnieuw binnenkomen. Als het signaal wordt onderbroken
terwijl Perl één functie uitvoert (zoals malloc(3) of printf(3)), en uw signaal
handler vervolgens dezelfde functie opnieuw aanroept, kunt u onvoorspelbaar gedrag krijgen - vaak: a
kern dump. Ten tweede is Perl zelf niet opnieuw actief op de laagste niveaus. Als het signaal
onderbreekt Perl terwijl Perl op dezelfde manier zijn eigen interne datastructuren verandert
onvoorspelbaar gedrag kan het gevolg zijn.
Er waren twee dingen die je kon doen als je dit wist: paranoïde zijn of pragmatisch zijn. De
paranoïde aanpak was om zo min mogelijk te doen in je signaalbehandelaar. Stel een bestaande in
integer-variabele die al een waarde heeft, en return. Dit helpt je niet als je binnen bent
een langzame systeemoproep, die gewoon opnieuw opstart. Dat betekent dat je eraan moet ‘sterven’ langjmp(3)
uit de behandelaar. Zelfs dit is een beetje arrogant voor de echte paranoïde, die vermijdt
"sterven" in een handler omdat het systeem is uit om je te halen. De pragmatische aanpak was om
zeg "Ik ken de risico's, maar geef de voorkeur aan het gemak", en doe alles wat u maar wilt
signaalbehandelaar, en wees bereid om zo nu en dan kerndumps op te ruimen.
Perl 5.8.0 en hoger vermijden deze problemen door signalen "uit te stellen". Dat wil zeggen, wanneer de
signaal wordt door het systeem aan het proces geleverd (aan de C-code die Perl implementeert)
vlag wordt ingesteld en de handler keert onmiddellijk terug. Vervolgens op strategische "veilige" punten in de
Perl-interpreter (bijvoorbeeld wanneer het op het punt staat een nieuwe opcode uit te voeren) worden de vlaggen gecontroleerd en
de Perl-niveauhandler van %SIG wordt uitgevoerd. De "uitgestelde" regeling maakt veel meer mogelijk
flexibiliteit bij het coderen van signaalbehandelaars, omdat we weten dat de Perl-tolk zich in een kluis bevindt
staat, en dat we ons niet in een systeembibliotheekfunctie bevinden wanneer de handler wordt aangeroepen.
De implementatie verschilt echter op de volgende manieren van eerdere Perls:
Langlopende opcodes
Omdat de Perl-interpreter alleen naar signaalvlaggen kijkt als hij op het punt staat een nieuwe uit te voeren
opcode, een signaal dat binnenkomt tijdens een langlopende opcode (bijvoorbeeld een reguliere expressie
bewerking op een zeer grote string) zal pas zichtbaar zijn als de huidige opcode is voltooid.
Als een signaal van een bepaald type meerdere keren wordt afgevuurd tijdens een opcode (zoals van een
fijnkorrelige timer), zal de handler voor dat signaal slechts één keer worden opgeroepen, na de
opcode voltooid; alle andere exemplaren worden verwijderd. Bovendien, als uw
De signaalwachtrij van het systeem wordt overspoeld tot het punt dat er signalen zijn geweest
verhoogd maar nog niet opgevangen (en dus niet uitgesteld) op het moment dat een opcode is voltooid,
deze signalen kunnen mogelijk worden opgevangen en uitgesteld tijdens daaropvolgende opcodes
soms verrassende resultaten. Het is bijvoorbeeld mogelijk dat alarmen zelfs daarna worden afgeleverd
bellen alarm(0) omdat deze laatste het genereren van alarmen stopt, maar het alarm niet annuleert
levering van alarmsignalen die zijn afgegeven maar nog niet zijn opgemerkt. Wees niet afhankelijk van het gedrag
die in deze paragraaf worden beschreven, aangezien dit bijwerkingen zijn van de huidige implementatie en
kan veranderen in toekomstige versies van Perl.
IO onderbreken
Wanneer een signaal wordt afgeleverd (bijvoorbeeld SIGINT van een control-C) breekt het besturingssysteem
in IO-operaties zoals dit artikel lezen(2), dat wordt gebruikt om Perl's te implementeren Lees regel()
functie, de operator "<>". Bij oudere Perls werd de handler onmiddellijk gebeld (en zoals
"lezen" is niet "onveilig", dit werkte goed). Bij het "uitgestelde" schema is dat de afhandelaar
niet onmiddellijk aangeroepen, en als Perl de "stdio"-bibliotheek van het systeem gebruikt, wordt die bibliotheek gebruikt
kan het "lezen" opnieuw starten zonder terug te keren naar Perl om het de kans te geven de %SIG aan te roepen
begeleider. Als dit op uw systeem gebeurt, is de oplossing om de laag ":perlio" te gebruiken
doe IO - tenminste op die handvatten waar je met signalen in wilt kunnen breken.
(De ":perlio"-laag controleert de signaalvlaggen en roept %SIG-handlers aan voordat het wordt hervat
IO-bediening.)
De standaard in Perl 5.8.0 en hoger is om automatisch de laag ":perlio" te gebruiken.
Merk op dat het niet raadzaam is om toegang te krijgen tot een bestandsingang binnen een signaalbehandelaar waar
dat signaal heeft een I/O-bewerking op diezelfde handle onderbroken. Terwijl perl dat zal doen
doe in ieder geval je best om niet te crashen, er zijn geen garanties voor de gegevensintegriteit; Bijvoorbeeld,
sommige gegevens kunnen verloren gaan of tweemaal worden geschreven.
Sommige netwerkbibliotheekfuncties zoals gethostbijnaam() staan erom bekend dat ze hun eigen hebben
implementaties van time-outs die in conflict kunnen komen met uw time-outs. Als je hebt
problemen met dergelijke functies, probeer dan de POSIX sigactie() functie, die omzeilt
Perl veilige signalen. Wees gewaarschuwd dat dit u blootstelt aan mogelijk geheugen
corruptie, zoals hierboven beschreven.
In plaats van $SIG{ALRM} in te stellen:
lokaal $SIG{ALRM} = sub {die "alarm" };
probeer zoiets als het volgende:
gebruik POSIX qw(SIGALRM);
POSIX::sigaction(SIGALRM, POSIX::SigAction->new(sub { die "alarm" }))
|| die "Fout bij het instellen van de SIGALRM-handler: $!\n";
Een andere manier om het veilige signaalgedrag lokaal uit te schakelen is door gebruik te maken van de
"Perl::Unsafe::Signals" module van CPAN, die alle signalen beïnvloedt.
Herstartbare systeemoproepen
Op systemen die dit ondersteunden, gebruikten oudere versies van Perl de vlag SA_RESTART wanneer
%SIG-handlers installeren. Dit betekende dat herstartbare systeemaanroepen zouden doorgaan
in plaats van terug te keren als er een signaal arriveerde. Om uitgestelde signalen af te geven
Perl 5.8.0 en hoger doen dat onmiddellijk niet gebruik SA_RESTART. Herstartbaar dus
systeemaanroepen kunnen mislukken (met $! ingesteld op "EINTR") op plaatsen waar dit voorheen zou gebeuren
Geslaagd zijn.
De standaard ":perlio"-laag probeert opnieuw "lezen", "schrijven" en "sluiten" zoals hierboven beschreven;
onderbroken "wait"- en "waitpid"-oproepen worden altijd opnieuw geprobeerd.
Signalen als "storingen"
Bepaalde signalen zoals SEGV, ILL en BUS worden gegenereerd door virtuele geheugenadressering
fouten en soortgelijke "fouten". Deze zijn normaal gesproken fataal: er is weinig Perl-niveau
begeleider kan ermee doen. Perl levert ze dus onmiddellijk af, in plaats van te proberen
stel ze uit.
Signalen geactiveerd door de status van het besturingssysteem
Op sommige besturingssystemen worden bepaalde signaalbehandelaars verondersteld "iets te doen"
voordat u terugkeert. Een voorbeeld kan CHLD of CLD zijn, wat aangeeft dat er sprake is van een kindproces
voltooid. Op sommige besturingssystemen wordt verwacht dat de signaalbehandelaar "wacht" op de
voltooid kindproces. Op dergelijke systemen zal het uitgestelde signaalschema niet werken
deze signalen: het "wacht" niet. Opnieuw zal de fout eruit zien als een lus
het besturingssysteem zal het signaal opnieuw geven omdat er een voltooid kind is
processen waarop nog niet is "gewacht".
Als u ondanks mogelijke geheugenbeschadiging het oude signaalgedrag terug wilt, stelt u de
omgevingsvariabele "PERL_SIGNALS" naar "onveilig". Deze functie verscheen voor het eerst in Perl
5.8.1.
Genoemd Pijpen
Een benoemde pipe (vaak een FIFO genoemd) is een oud Unix IPC-mechanisme voor processen
communiceren op dezelfde machine. Het werkt net als gewone anonieme pijpen, behalve
dat de processen samenkomen met behulp van een bestandsnaam en niet gerelateerd hoeven te zijn.
Om een benoemde pipe te maken, gebruikt u de functie "POSIX::mkfifo()".
gebruik POSIX qw(mkfifo);
mkfifo($pad, 0700) || die "mkfifo $pad mislukt: $!";
U kunt ook het Unix-commando gebruiken mknoden(1), of op sommige systemen, mkfifo(1). Dit is mogelijk niet het geval
wees echter op uw normale pad.
# system return val is achteruit, dus && niet ||
#
$ENV{PATH} .= ":/ Etc:/usr/etc";
if ( system("mknod", $pad, "p")
&& systeem("mkfifo", $pad) )
{
die "mk{nod,fifo} $pad mislukt";
}
Een fifo is handig als u een proces aan een niet-gerelateerd proces wilt koppelen. Wanneer je
open een fifo, het programma blokkeert totdat er iets aan de andere kant is.
Stel dat u bijvoorbeeld uw .handtekening bestand een benoemde pipe zijn met een
Perl-programma aan de andere kant. Elke keer dat een programma (zoals een mailer, nieuwslezer,
Finger-programma, enz.) uit dat bestand probeert te lezen, zal het leesprogramma het nieuwe bestand lezen
handtekening van uw programma. We gebruiken de pipe-checking file-test operator, -p, vinden
of iemand (of iets) per ongeluk onze fifo heeft verwijderd.
chdir(); # ga naar huis
mijn $FIFO = ".handtekening";
terwijl (1) {
tenzij (-p $FIFO) {
$FIFO ontkoppelen; # gooi elke fout weg, deze wordt later opgevangen
POSIX vereisen; # vertraagd laden van zware module
POSIX::mkfifo($FIFO, 0700)
|| die "kan $FIFO: $ niet mkfifo!";
}
# volgende regelblokken totdat er een lezer is
open (FIFO, "> $FIFO") || die "kan $FIFO: $ niet openen!";
print FIFO "John Smith (smith\@host.org)\n", `fortune -s`;
sluiten(FIFO) || die "kan $FIFO: $ niet sluiten!";
slaap 2; # om dup-signalen te voorkomen
}
gebruik Open() voor IPC
Perl is de basis Open() statement kan ook worden gebruikt voor unidirectionele interprocess
communicatie door een pijpsymbool aan het tweede argument toe te voegen of ervoor te plaatsen
Open(). Zo start u iets in een onderliggend proces waarnaar u wilt schrijven:
open(SPOOLER, "| cat -v | lpr -h 2>/dev/null")
|| die "kan niet splitsen: $!";
local $SIG{PIPE} = sub {die "spoolerpijp kapot" };
print SPOOLER "dingen\n";
sluit SPOOLER || sterven "slechte spoel: $! $?";
En zo start u een onderliggend proces waaruit u wilt lezen:
open(STATUS, "netstat -een 2>&1 |")
|| die "kan niet splitsen: $!";
terwijl ( ) {
volgende als /^(tcp|udp)/;
afdrukken;
}
sluit STATUS || die "slechte netstat: $! $?";
Als je er zeker van kunt zijn dat een bepaald programma een Perl-script is waarin bestandsnamen worden verwacht
@ARGV, de slimme programmeur kan zoiets als dit schrijven:
% programma f1 "cmd1|" -f2 "cmd2|" f3 <tmpbestand
en ongeacht vanuit welk soort shell het wordt aangeroepen, zal het Perl-programma lezen uit de
filet f1, het proces cmd1, standaardinvoer (tmpbestand in dit geval), de f2 bestand, het cmd2
opdracht, en ten slotte de f3 bestand. Best handig, hè?
Het zal je misschien opvallen dat je backticks kunt gebruiken voor vrijwel hetzelfde effect als het openen van een pijp
voor lezen:
print grep { !/^(tcp|udp)/ } `netstat -an 2>&1`;
sterven "slechte netstatus ($?)" als $?;
Hoewel dit op het eerste gezicht waar is, is het veel efficiënter om het bestand met één regel te verwerken
of neem het per keer op, want dan hoef je niet het hele ding in het geheugen te lezen
eenmaal. Het geeft u ook een betere controle over het hele proces, waardoor u de problemen kunt beëindigen
kindproces vroeg als je wilt.
Zorg ervoor dat u de retourwaarden van beide controleert Open() en dichtbij(). Als je het schrijven van naar
een pijp, moet u ook SIGPIPE opvangen. Denk anders eens na over wat er gebeurt als u opstart
een pipe naar een commando dat niet bestaat: de Open() zal naar alle waarschijnlijkheid slagen (alleen
weerspiegelt de vork()'s succes), maar dan zal je output mislukken - op spectaculaire wijze. Perl kan dat niet
weet of de opdracht heeft gewerkt, omdat uw opdracht daadwerkelijk in een afzonderlijk bestand wordt uitgevoerd
proces waarvan exec () misschien mislukte. Daarom keren lezers van valse commando's terug
gewoon een snelle EOF, schrijvers van valse commando's zullen een signaal krijgen, wat ze het beste kunnen doen
wees bereid om te hanteren. Overwegen:
open(FH, "|nep") || die "kan niet splitsen: $!";
print FH "bang\n"; # noch noodzakelijk noch voldoende
# om het ophalen van afdrukken te controleren!
sluiten(FH) || die "kan niet sluiten: $!";
De reden voor het niet controleren van de geretourneerde waarde van afdrukken() komt door pijpbuffering;
fysieke schrijfbewerkingen worden vertraagd. Dat zal pas aan het eind ontploffen, en het zal mee opblazen
een SIGPIPE. Om het op te vangen, kun je dit gebruiken:
$SIG{PIPE} = "NEGEREN";
open(FH, "|nep") || die "kan niet splitsen: $!";
print FH "bang\n";
sluiten(FH) || die "kan niet sluiten: status=$?";
Bestandshandvatten
Zowel het hoofdproces als eventuele onderliggende processen die het afsplitst, delen dezelfde STDIN, STDOUT en
STDERR-bestandshandles. Als beide processen er tegelijkertijd toegang toe proberen te krijgen, kunnen er vreemde dingen gebeuren
gebeuren. Mogelijk wilt u ook de filehandles voor het kind sluiten of opnieuw openen. Je kan krijgen
hieromheen door je pijp te openen met Open(), maar op sommige systemen betekent dit dat de
Het kindproces kan de ouder niet overleven.
Achtergrond processen
U kunt een opdracht op de achtergrond uitvoeren met:
systeem("cmd &");
De commando's STDOUT en STDERR (en mogelijk STDIN, afhankelijk van je shell) zullen de
hetzelfde als die van de ouders. U hoeft SIGCHLD niet te vangen vanwege het nemen van dubbele vorken
plaats; zie hieronder voor meer informatie.
Volledige Dissociatie of Kind van Ouder
In sommige gevallen (bijvoorbeeld bij het starten van serverprocessen) wilt u dit volledig doen
het kindproces loskoppelen van het ouderproces. Dit wordt vaak daemonisatie genoemd. A
goed opgevoede daemon zal dat ook doen chdir() naar de hoofdmap, zodat dit niet wordt voorkomen
het ontkoppelen van het bestandssysteem dat de map bevat van waaruit het werd gestart, en
de standaardbestandsdescriptors van en naar omleiden / Dev / null zodat willekeurige uitvoer dat niet doet
op de terminal van de gebruiker terechtkomen.
gebruik POSIX "setsid";
sub-daemoniseren {
chdir("/") || die "kan niet chdiren naar /: $!";
open(STDIN, "< /dev/null") || die "kan /dev/null: $ niet lezen!";
open(STDOUT, "> /dev/null") || die "kan niet schrijven naar /dev/null: $!";
gedefinieerd(mijn $pid = fork()) || die "kan niet splitsen: $!";
afsluiten als $pid; # niet-nul betekent nu dat ik de ouder ben
(setsid() != -1) || die "Kan geen nieuwe sessie starten: $!";
open(STDERR, ">&STDOUT") || die "kan stdout niet duperen: $!";
}
De vork() moet vóór de komen setid() om ervoor te zorgen dat u geen procesgroepleider bent;
the setid() zal mislukken als je dat wel bent. Als uw systeem niet over de setid() functie,
open /dev/tty en gebruik de "TIOCNOTTY" ioctl() erop in plaats daarvan. Zien tty(4) voor details.
Niet-Unix-gebruikers moeten hun "Jouw_OS::Proces" module voor andere mogelijke oplossingen.
Kluis/ Safe Pijptabak Opent
Een andere interessante benadering van IPC is om uw enkele programma multiprocess te maken
communiceer tussen – of zelfs onder – uzelf. De Open() functie accepteert een bestand
argument van ofwel "-|" of "|-" om iets heel interessants te doen: het zorgt ervoor dat een kind verbonden is
naar de filehandle die u hebt geopend. Het kind gebruikt hetzelfde programma als de ouder.
Dit is handig voor het veilig openen van een bestand wanneer het onder een veronderstelde UID of GID draait, bijvoorbeeld
voorbeeld. Als je een pijp opent naar minus, je kunt schrijven naar de filehandle die je hebt geopend en je
kind zal het vinden zijn STDIN. Als je een pijp opent van minus, je kunt lezen van de
filehandle je hebt geopend waar je kind naar schrijft zijn STDOUT.
gebruik Engels;
mijn $PRECIOUS = "/pad/naar/een/veilig/bestand";
mijn $sleep_count;
mijn $pid;
Doen {
$pid = open(KID_TO_WRITE, "|-");
tenzij (gedefinieerd $pid) {
waarschuwing "kan niet forken: $!";
sterf "redding" als $sleep_count++ > 6;
slaap 10;
}
} totdat gedefinieerd $pid;
if ($pid) { # Ik ben de ouder
print KID_TO_WRITE @some_data;
sluiten(KID_TO_WRITE) || waarschuwen "kind heeft $ verlaten?";
} else { # Ik ben het kind
# drop-rechten in setuid- en/of setgid-programma's:
($EUID, $EGID) = ($UID, $GID);
open (OUTFILE, "> $PRECIOUS")
|| die "kan $PRECIOUS: $ niet openen!";
terwijl ( ) {
OUTFILE afdrukken; De STDIN van # kind is de KID_TO_WRITE van de ouder
}
sluiten(OUTFILE) || die "kan $PRECIOUS: $ niet sluiten!";
afrit(0); #vergeet dit niet!!
}
Een ander veelgebruikt gebruik van deze constructie is wanneer je iets moet uitvoeren zonder de
Shell's inmenging. Met systeem(), het is eenvoudig, maar je kunt geen open pijp gebruiken
of backticks veilig. Dat komt omdat er geen manier is om te voorkomen dat de schaal het krijgt
handen op uw argumenten. Gebruik in plaats daarvan de besturing op een lager niveau om te bellen exec () direct.
Hier is een veilige backtick of pijp open om te lezen:
mijn $pid = open(KID_TO_READ, "-|");
gedefinieerd($pid) || die "kan niet splitsen: $!";
if ($pid) { # ouder
terwijl ( ) {
#doe iets interessants
}
sluiten(KID_TO_READ) || waarschuwen "kind heeft $ verlaten?";
} anders { # kind
($EUID, $EGID) = ($UID, $GID); Alleen # suid
exec($programma, @opties, @args)
|| die "kan programma niet uitvoeren: $!";
# NIET GEHAALD
}
En hier is een veilige pijp om te schrijven:
mijn $pid = open(KID_TO_WRITE, "|-");
gedefinieerd($pid) || die "kan niet splitsen: $!";
$SIG{PIPE} = sub { die "oeps, $programmapijp kapot" };
if ($pid) { # ouder
print KID_TO_WRITE @data;
sluiten(KID_TO_WRITE) || waarschuwen "kind heeft $ verlaten?";
} anders { # kind
($EUID, $EGID) = ($UID, $GID);
exec($programma, @opties, @args)
|| die "kan programma niet uitvoeren: $!";
# NIET GEHAALD
}
Het is heel gemakkelijk om een proces te blokkeren met deze vorm van Open(), of zelfs met enig nut
of pijp() met meerdere subprocessen. Het bovenstaande voorbeeld is "veilig" omdat het eenvoudig is
en oproepen exec (). Zie "Het vermijden van pijpimpasses" voor algemene veiligheidsprincipes, maar daar
zijn extra valkuilen met Safe Pipe Opens.
In het bijzonder, als je de pijp hebt geopend met "open FH, "|-"", dan kun je niet zomaar gebruiken
dichtbij() in het bovenliggende proces om een ongewenste schrijver te sluiten. Overweeg deze code:
mijn $pid = open(WRITER, "|-"); # vork open een kind
gedefinieerd($pid) || die "eerste vork mislukt: $!";
als ($pid) {
if (mijn $sub_pid = fork()) {
gedefinieerd($sub_pid) || die "tweede vork mislukt: $!";
sluiten(SCHRIJVER) || die "kon WRITER: $ niet afsluiten!";
#doe nu iets anders...
}
else {
# schrijf eerst naar WRITER
#...
# en dan wanneer u klaar bent
sluiten(SCHRIJVER) || die "kon WRITER: $ niet afsluiten!";
afrit(0);
}
}
else {
#doe dan eerst iets met STDIN
afrit(0);
}
In het bovenstaande voorbeeld wil de echte ouder niet naar de WRITER-bestandshandle schrijven, dus
het sluit het. Omdat WRITER echter werd geopend met "open FH, "|-"", heeft het een speciaal
gedrag: het sluiten roept wachtend() (zie "waitpid" in perlfunc), dat wacht op de
subproces om af te sluiten. Als het kindproces uiteindelijk wacht tot er iets gebeurt in de
sectie gemarkeerd met "doe iets anders", is er sprake van een impasse.
Dit kan ook een probleem zijn bij tussenliggende subprocessen in meer gecompliceerde code
zal bellen wachtend() op alle open filehandles tijdens wereldwijde vernietiging - op geen enkele manier voorspelbaar
order.
Om dit op te lossen, moet u handmatig gebruiken pijp(), vork()en de vorm van Open() die er een instelt
bestandsdescriptor naar een andere, zoals hieronder weergegeven:
pipe(LEZER, SCHRIJVER) || die "pijp mislukt: $!";
$pid = vork();
gedefinieerd($pid) || die "eerste vork mislukt: $!";
als ($pid) {
sluit LEZER;
if (mijn $sub_pid = fork()) {
gedefinieerd($sub_pid) || die "eerste vork mislukt: $!";
sluiten(SCHRIJVER) || die "kan WRITER: $ niet sluiten!";
}
else {
# schrijf naar SCHRIJVER...
#...
# en dan wanneer u klaar bent
sluiten(SCHRIJVER) || die "kan WRITER: $ niet sluiten!";
afrit(0);
}
# schrijf naar SCHRIJVER...
}
else {
open(STDIN, "<&READER") || die "kan STDIN: $ niet heropenen!";
sluiten(SCHRIJVER) || die "kan WRITER: $ niet sluiten!";
# doe iets...
afrit(0);
}
Sinds Perl 5.8.0 kunt u voor pipelines ook de lijstvorm "open" gebruiken. Dit heeft de voorkeur
als je wilt voorkomen dat de shell metatekens interpreteert die mogelijk in je bestand voorkomen
opdrachtreeks.
Dus in plaats van bijvoorbeeld:
open(PS_PIPE, "ps aux|") || die "kan ps pipe niet openen: $!";
Je zou een van deze gebruiken:
open(PS_PIPE, "-|", "ps", "aux")
|| die "kan ps pipe niet openen: $!";
@ps_args = qw[ ps aux ];
open(PS_PIPE, "-|", @ps_args)
|| die "kan @ps_args|: $ niet openen!";
Omdat er meer dan drie argumenten zijn Open(), vorken de ps(1) bevel zonder
een shell voortbrengen, en leest de standaarduitvoer ervan via de bestandshandle "PS_PIPE". De
overeenkomstige syntaxis schrijven Pipes commando's geven is het gebruik van "|-" in plaats van "-|".
Dit was weliswaar een nogal dom voorbeeld, omdat je tekenreeksletterlijke waarden gebruikt waarvan
inhoud is volkomen veilig. Er is daarom geen reden om toevlucht te nemen tot de moeilijker leesbare,
multi-argumentvorm van pijp Open(). Wanneer u er echter niet zeker van kunt zijn dat de
programmaargumenten zijn vrij van shell-metakarakters, de luxere vorm van Open() moet
gebruikt. Bijvoorbeeld:
@grep_args = ("egrep", "-i", $some_pattern, @many_files);
open(GREP_PIPE, "-|", @grep_args)
|| die "kan @grep_args|: $ niet openen!";
Hier de multi-argumentvorm van pijp Open() heeft de voorkeur vanwege het patroon en inderdaad
zelfs de bestandsnamen zelf kunnen metatekens bevatten.
Houd er rekening mee dat deze bewerkingen volledige Unix-vorken zijn, wat betekent dat ze mogelijk niet correct zijn
geïmplementeerd op alle buitenaardse systemen.
Het vermijden van Pijptabak impasses
Wanneer u meer dan één subproces heeft, moet u erop letten dat elk subproces wordt afgesloten
de helft van alle leidingen die zijn gemaakt voor communicatie tussen processen, wordt niet gebruikt. Dit is zo omdat
elk kindproces dat uit de pijp leest en een EOF verwacht, zal het nooit ontvangen, en
ga er dus nooit uit. Eén enkel proces om een leiding te sluiten is niet voldoende om deze te sluiten; de laatste
proces met de pijp open moet deze sluiten om EOF te kunnen lezen.
Bepaalde ingebouwde Unix-functies helpen dit meestal te voorkomen. Bijvoorbeeld,
filehandles hebben een vlag "close on exec", die is ingesteld en massa onder controle van de $^F
variabel. Dit is zodat alle bestandshandles die u niet expliciet naar de STDIN, STDOUT of
STDERR van een kind programma wordt automatisch gesloten.
Bel altijd expliciet en direct dichtbij() op het beschrijfbare uiteinde van een pijp, tenzij
dat proces schrijft er feitelijk naar. Ook als je niet expliciet belt dichtbij(), Perl
zal nog steeds dichtbij() alle filehandles tijdens wereldwijde vernietiging. Zoals eerder besproken, als
die filehandles zijn geopend met Safe Pipe Open, dit zal resulteren in een aanroep
wachtend(), die opnieuw in een impasse kan raken.
bidirectionele Communicatie met Nog een Proces
Hoewel dit redelijk goed werkt voor unidirectionele communicatie, hoe zit het dan?
bidirectionele communicatie? De meest voor de hand liggende aanpak werkt niet:
# DIT WERKT NIET!!
open(PROG_FOR_READING_AND_WRITING, "| een programma |")
Als u vergeet "waarschuwingen te gebruiken", loopt u de nuttige diagnose volledig mis
bericht:
Kan geen bidirectionele pijp uitvoeren op -e lijn 1.
Als je het echt wilt, kun je de standaard gebruiken openen2() van de module "IPC::Open2" naar
beide uiteinden opvangen. Er is ook een openen3() in "IPC::Open3" voor tridirectionele I/O zodat u
kan ook de STDERR van uw kind opvangen, maar dit zou dan een ongemakkelijke situatie vereisen selecteer()
loop en zou u niet toestaan normale Perl-invoerbewerkingen te gebruiken.
Als je naar de bron kijkt, zie je dat openen2() maakt gebruik van primitieven op laag niveau, zoals de
pijp() en exec () syscalls om alle verbindingen te maken. Hoewel dat misschien wel zo was
efficiënter door te gebruiken socketpaar(), zou dit zelfs nog minder draagbaar zijn geweest dan het
is het al. De openen2() en openen3() Het is onwaarschijnlijk dat functies ergens werken, behalve op a
Unix-systeem, of ten minste één zogenaamde POSIX-compatibiliteit.
Hier is een voorbeeld van gebruik openen2():
gebruik FileHandle;
gebruik IPC::Open2;
$pid = open2(*Lezer, *Schrijver, "cat -un");
print Schrijver "dingen\n";
$gekregen = ;
Het probleem hiermee is dat bufferen je dag echt gaat verpesten. Zelfs
uw "Writer"-bestandshandle wordt automatisch leeggemaakt, zodat het proces aan de andere kant uw gegevens binnenhaalt
op tijd, kunt u normaal gesproken niets doen om dat proces te dwingen zijn gegevens aan te geven
u op een vergelijkbare snelle manier. In dit speciale geval zouden we dat inderdaad kunnen doen, omdat we
gaf hoe a -u vlag om het ongebufferd te maken. Maar heel weinig commando's zijn ontworpen om te werken
overpipes, dus dit werkt zelden, tenzij je zelf het programma aan de andere kant van hebt geschreven
de dubbelwandige pijp.
Een oplossing hiervoor is het gebruik van een bibliotheek die pseudotty's gebruikt om uw programma zich te laten gedragen
redelijker. Op deze manier hoeft u geen controle te hebben over de broncode van de
programma dat u gebruikt. De module "Expect" van CPAN behandelt dit soort zaken ook.
Deze module vereist twee andere modules van CPAN, "IO::Pty" en "IO::Stty". Het stelt een
pseudo-terminal om te communiceren met programma's die erop staan om met het eindapparaat te praten
bestuurder. Als uw systeem wordt ondersteund, is dit wellicht de beste keuze.
bidirectionele Communicatie met Jezelf
Als je wilt, kun je low-level maken pijp() en vork() syscalls om dit samen te voegen
hand. Dit voorbeeld praat alleen tegen zichzelf, maar u kunt de betreffende handvatten opnieuw openen
STDIN en STDOUT en roep andere processen aan. (In het volgende voorbeeld ontbreekt de juiste fout
controleren.)
#!/usr/bin/perl -w
# pipe1 - bidirectionele communicatie met behulp van twee pijpparen
# ontworpen voor de socketpair-uitdaging
gebruik IO::Handvat; # duizenden regels alleen voor automatisch doorspoelen :-(
pijp(PARENT_RDR, CHILD_WTR); # XXX: controle mislukt?
pijp(CHILD_RDR, PARENT_WTR); # XXX: controle mislukt?
CHILD_WTR->automatisch spoelen(1);
PARENT_WTR->automatisch spoelen(1);
als ($pid = fork()) {
sluit PARENT_RDR;
sluit PARENT_WTR;
print CHILD_WTR "Ouder Pid $$ verzendt dit\n";
chomp($regel = );
print "Ouder Pid $$ lees dit: '$line'\n";
sluit CHILD_RDR; sluit CHILD_WTR;
wachtpid($pid, 0);
} Else {
sterven "kan niet splitsen: $!" tenzij gedefinieerd $pid;
sluit CHILD_RDR;
sluit CHILD_WTR;
chomp($regel = );
print "Child Pid $$ lees dit: '$line'\n";
print PARENT_WTR "Kindpid $$ verzendt dit\n";
sluit PARENT_RDR;
sluit PARENT_WTR;
afrit(0);
}
Maar je hoeft niet echt twee keer te bellen. Als je de socketpaar() system
bel, het zal dit allemaal voor u doen.
#!/usr/bin/perl -w
# pipe2 - bidirectionele communicatie met behulp van socketpair
# "de beste gaan altijd beide kanten op"
gebruik stopcontact;
gebruik IO::Handvat; # duizenden regels alleen voor automatisch doorspoelen :-(
# We zeggen AF_UNIX omdat hoewel *_LOCAL de
# POSIX 1003.1g-vorm van de constante, veel machines
# heb het nog steeds niet.
socketpaar(CHILD, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
|| die "socketpaar: $!";
KIND->automatisch spoelen(1);
OUDER->automatisch spoelen(1);
als ($pid = fork()) {
dichtbij OUDER;
print CHILD "Ouder Pid $$ verzendt dit\n";
chomp($regel = );
print "Ouder Pid $$ lees dit: '$line'\n";
sluit KIND;
wachtpid($pid, 0);
} Else {
sterven "kan niet splitsen: $!" tenzij gedefinieerd $pid;
sluit KIND;
chomp($regel = );
print "Child Pid $$ lees dit: '$line'\n";
print OUDER "Kind Pid $$ verzendt dit\n";
dichtbij OUDER;
afrit(0);
}
Sockets: Client server Communicatie
Hoewel niet volledig beperkt tot van Unix afgeleide besturingssystemen (bijv. WinSock op pc's).
socketondersteuning biedt, zoals sommige VMS-bibliotheken doen), beschikt u mogelijk niet over sockets op uw
systeem, in welk geval deze sectie je waarschijnlijk niet veel goeds zal opleveren. Met
sockets kunt u zowel virtuele circuits zoals TCP-streams als datagrammen zoals UDP-pakketten uitvoeren.
Afhankelijk van uw systeem kunt u wellicht nog meer doen.
De Perl-functies voor het omgaan met sockets hebben dezelfde namen als de overeenkomstige
systeemaanroepen in C, maar hun argumenten verschillen om twee redenen. Eerst Perl
filehandles werken anders dan C-bestandsdescriptors. Ten tweede kent Perl de
lengte van de tekenreeksen, zodat u die informatie niet hoeft door te geven.
Een van de grootste problemen met oude, antemillenniale socketcode in Perl was dat deze
gebruikte hardgecodeerde waarden voor sommige constanten, wat de draagbaarheid ernstig schaadde. als jij
ooit code ziet die zoiets doet als het expliciet instellen van "$AF_INET = 2", weet je dat dat zo is
voor grote problemen. Een onmetelijk superieure aanpak is het gebruik van de "Socket"-module,
waarmee u op betrouwbaardere wijze toegang krijgt tot de verschillende constanten en functies die u nodig heeft.
Als u geen server/client schrijft voor een bestaand protocol zoals NNTP of SMTP, kunt u
moet nadenken over hoe uw server weet wanneer de client klaar is
praten, en omgekeerd. De meeste protocollen zijn gebaseerd op berichten en antwoorden van één regel (dus
de ene partij weet dat de andere klaar is wanneer een "\n" wordt ontvangen) of berichten met meerdere regels en
reacties die eindigen met een punt op een lege regel ("\n.\n" beëindigt een bericht/antwoord).
Internet Lijn terminators
De internetlijnterminator is "\015\012". Onder ASCII-varianten van Unix zou dat wel kunnen
wordt meestal geschreven als "\r\n", maar onder andere systemen kan "\r\n" soms zo zijn
"\015\015\012", "\012\012\015", of iets heel anders. De normen specificeren
het schrijven van "\015\012" om conform te zijn (wees streng in wat u verstrekt), maar zij ook
raad aan om een enkele "\012" bij invoer te accepteren (wees mild in wat u nodig heeft). Dat hebben we niet gedaan
ben daar altijd heel goed in geweest in de code op deze manpagina, maar tenzij je een Mac gebruikt
van lang geleden, in de pre-Unix donkere middeleeuwen, komt alles waarschijnlijk wel goed met je.
Internet TCP Klanten en Servers
Gebruik internetdomeinsockets als u client-servercommunicatie wilt uitvoeren
uitbreiden naar machines buiten uw eigen systeem.
Hier is een voorbeeld van een TCP-client die internetdomeinsockets gebruikt:
#!/usr/bin/perl -w
gebruik strikt;
gebruik stopcontact;
mijn ($remote, $port, $iaddr, $paddr, $proto, $line);
$op afstand = dienst || "lokale host";
$poort = verschuiven || 2345; # willekeurige poort
if ($port =~ /\D/) { $port = getservbyname($port, "tcp") }
sterf "Geen poort" tenzij $port;
$iaddr = inet_aton($remote) || sterven "geen host: $remote";
$paddr = sockaddr_in($port, $iaddr);
$proto = getprotonaam("tcp");
socket(SOCK, PF_INET, SOCK_STREAM, $proto) || sterven "socket: $!";
connect(SOCK, $paddr) || sterven "verbinden: $!";
terwijl ($lijn = ) {
print $regel;
}
sluiten (SOK) || sterven "sluiten: $!";
afrit(0);
En hier is een bijbehorende server die daarbij hoort. We laten het adres achter als
"INADDR_ANY" zodat de kernel de juiste interface op multihomed-hosts kan kiezen.
Als je op een bepaalde interface wilt zitten (zoals de externe kant van een gateway of firewall
machine), vul dit dan in met uw echte adres.
#!/usr/bin/perl -Tw
gebruik strikt;
BEGIN { $ENV{PATH} = "/ Usr / bin:/ bin" }
gebruik stopcontact;
gebruik karper;
mijn $EOL = "\015\012";
sub logmsg { print "$0 $$: @_ at ", scalaire lokale tijd(), "\n" }
mijn $poort = shift || 2345;
die "ongeldige poort" tenzij $port =~ /^ \d+ $/x;
mijn $proto = getprotobyname("tcp");
socket(Server, PF_INET, SOCK_STREAM, $proto) || sterven "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
|| sterven "setsockopt: $!";
bind(Server, sockaddr_in($poort, INADDR_ANY)) || sterven "binden: $!";
luister(Server, SOMAXCONN) || sterf "luister: $!";
logmsg "server gestart op poort $poort";
mijn $paddr;
$SIG{CHLD} = \&REAPER;
for (; $paddr = accept(Client, Server); client sluiten) {
mijn($poort, $iaddr) = sockaddr_in($paddr);
mijn $naam = gethostbyaddr($iaddr, AF_INET);
logmsg "verbinding van $name [",
inet_ntoa($iaddr), "]
op poort $poort";
print Client "Hallo daar, $name, het is nu ",
scalaire lokale tijd(), $EOL;
}
En hier is een multitasking-versie. Het is daarin multitasked, zoals de meeste typische servers
spawnt (vork()s) een slave-server om het clientverzoek af te handelen, zodat de master-server dat kan
snel weer een nieuwe klant bedienen.
#!/usr/bin/perl -Tw
gebruik strikt;
BEGIN { $ENV{PATH} = "/ Usr / bin:/ bin" }
gebruik stopcontact;
gebruik karper;
mijn $EOL = "\015\012";
subspawn; # voorwaartse verklaring
sub logmsg { print "$0 $$: @_ at ", scalaire lokale tijd(), "\n" }
mijn $poort = shift || 2345;
die "ongeldige poort" tenzij $port =~ /^ \d+ $/x;
mijn $proto = getprotobyname("tcp");
socket(Server, PF_INET, SOCK_STREAM, $proto) || sterven "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1))
|| sterven "setsockopt: $!";
bind(Server, sockaddr_in($poort, INADDR_ANY)) || sterven "binden: $!";
luister(Server, SOMAXCONN) || sterf "luister: $!";
logmsg "server gestart op poort $poort";
mijn $waitedpid = 0;
mijn $paddr;
gebruik POSIX ":sys_wait_h";
gebruik Errno;
sub MAAIER {
lokale $!; # laat waitpid() de huidige fout niet overschrijven
while ((mijn $pid = waitpid(-1, WNOHANG)) > 0 && WIFEXITED($?)) {
logmsg "geoogst $waitedpid" . ($? ? " met uitgang $?" : "");
}
$SIG{CHLD} = \&REAPER; #hekel aan SysV
}
$SIG{CHLD} = \&REAPER;
terwijl (1) {
$paddr = accepteren(Client, Server) || Doen {
# probeer het opnieuw als accept() terugkomt omdat er een signaal is ontvangen
volgende als $!{EINTR};
sterven "accepteren: $!";
};
mijn ($poort, $iaddr) = sockaddr_in($paddr);
mijn $naam = gethostbyaddr($iaddr, AF_INET);
logmsg "verbinding van $name [",
inet_ntoa($iaddr),
"] op poort $poort";
spawn-sub {
$| = 1;
print "Hallo daar, $name, het is nu ", scalar localtime(), $EOL;
exec "/usr/games/fortune" # XXX: "verkeerde" lijnafsluitingen
of bekennen "kan fortuin niet uitvoeren: $!";
};
klant sluiten;
}
sub-spawn {
mijn $coderef = shift;
tenzij (@_ == 0 && $coderef && ref($coderef) eq "CODE") {
beken "gebruik: spawn CODEREF";
}
mijn $pid;
tenzij (gedefinieerd($pid = vork())) {
logmsg "kan niet forken: $!";
terug te keren;
}
elsif ($pid) {
logmsg "verwekte $pid";
opbrengst; # Ik ben de ouder
}
# anders ben ik het kind -- ga spawnen
open(STDIN, "<&Client") || die "kan client niet naar stdin duppen";
open(STDOUT, ">&Client") || die "kan client niet naar stdout leiden";
## open(STDERR, ">&STDOUT") || die "kan stdout niet naar stderr dup";
exit($coderef->());
}
Deze server neemt de moeite om een onderliggende versie via te klonen vork() voor elke inkomende
verzoek. Op die manier kan het veel verzoeken tegelijk afhandelen, wat je misschien niet altijd wilt.
Zelfs als je dat niet doet vork() luister() zal zoveel openstaande verbindingen mogelijk maken. Vorken
servers moeten bijzonder voorzichtig zijn bij het opruimen van hun dode kinderen (genaamd
"zombies" in Unix-taal), omdat je anders je procestabel snel vol krijgt.
Hier wordt de subroutine REAPER gebruikt om aan te roepen wachtend() voor alle onderliggende processen die dat wel hebben
voltooid, waardoor ervoor wordt gezorgd dat ze netjes eindigen en zich niet bij de gelederen van de voegen
levende doden.
Binnen de while-lus callen we aanvaarden() en controleer of er een valse waarde wordt geretourneerd. Dit
zou normaal gesproken betekenen dat er een systeemfout moet worden gerapporteerd. Echter, de introductie van
veilige signalen (zie "Uitgestelde signalen (veilige signalen)" hierboven) in Perl 5.8.0 betekent dat
aanvaarden() kan ook worden onderbroken wanneer het proces een signaal ontvangt. Dit is typisch
gebeurt wanneer een van de gevorkte subprocessen afsluit en het ouderproces op de hoogte stelt met a
CHLD-signaal.
If aanvaarden() wordt onderbroken door een signaal, $! wordt ingesteld op EINTR. Als dit gebeurt, kunnen we dat doen
veilig doorgaan naar de volgende iteratie van de lus en nog een oproep naar aanvaarden(). Het is
Het is belangrijk dat uw signaalverwerkingscode de waarde van $! of anders deze test niet wijzigt
zal waarschijnlijk mislukken. In de REAPER-subroutine maken we een lokale versie van $! voordat u belt
wachtend(). Wanneer wachtend() stelt $ in! aan ECHILD zoals het onvermijdelijk doet als het er niet meer heeft
wachtende kinderen, wordt de lokale kopie bijgewerkt en blijft het origineel ongewijzigd.
Gebruik de -T flag om besmettingscontrole in te schakelen (zie perlsec), zelfs als dat niet het geval is
met setuid of setgid. Dit is altijd een goed idee voor servers of welk programma dan ook
namens iemand anders (zoals CGI-scripts), omdat het de kansen verkleint dat mensen er vanaf komen
de buitenkant kan uw systeem in gevaar brengen.
Laten we eens naar een andere TCP-client kijken. Deze maakt verbinding met de TCP "time" -service op een nummer
van verschillende machines en laat zien hoe ver hun klokken verschillen van het systeem waarop het staat
wordt gerund:
#!/usr/bin/perl -w
gebruik strikt;
gebruik stopcontact;
mijn $SECS_OF_70_YEARS = 2208988800;
sub ctime { scalaire lokale tijd(shift() || tijd()) }
mijn $iaddr = gethostbyname("localhost");
mijn $proto = getprotobyname("tcp");
mijn $port = getservbyname("time", "tcp");
mijn $paddr = sockaddr_in(0, $iaddr);
mijn($host);
$| = 1;
printf "%-24s %8s %s\n", "localhost", 0, ctime();
foreach $host (@ARGV) {
printf "%-24s", $host;
mijn $hisiaddr = inet_aton($host) || sterf "onbekende gastheer";
mijn $hispaddr = sockaddr_in($port, $hisiaddr);
socket(SOCKET, PF_INET, SOCK_STREAM, $proto)
|| sterven "socket: $!";
connect(SOCKET, $hispaddr) || sterven "verbinden: $!";
mijn $rtime = pack("C4", ());
lees(SOCKET, $rtime, 4);
sluiten(SOCKET);
mijn $histime = uitpakken("N", $rtime) - $SECS_OF_70_YEARS;
printf "%8d %s\n", $histime - time(), ctime($histime);
}
Unix-domein TCP Klanten en Servers
Dat is prima voor internetdomeinclients en -servers, maar hoe zit het met lokale communicatie?
Hoewel je dezelfde configuratie kunt gebruiken, wil je dat soms niet. Unix-domein sockets zijn dat wel
lokaal voor de huidige host, en worden vaak intern gebruikt om leidingen te implementeren. in tegenstelling tot
Internetdomeinsockets, Unix-domeinsockets kunnen in het bestandssysteem verschijnen met een ls(1)
lijst.
% ls -l /dev/log
srw-rw-rw- 1 root 0 31 oktober 07:23 /dev/log
Je kunt dit testen met Perl's -S bestandstest:
tenzij (-S "/dev/log") {
sterf "er is iets mis met het logsysteem";
}
Hier is een voorbeeld van een Unix-domeinclient:
#!/usr/bin/perl -w
gebruik stopcontact;
gebruik strikt;
mijn ($rendez-vous, $line);
$rendezvous = verschuiven || "kattensok";
socket(SOCK, PF_UNIX, SOCK_STREAM, 0) || sterven "socket: $!";
connect(SOCK, sockaddr_un($rendezvous)) || sterven "verbinden: $!";
terwijl (gedefinieerd($line = )) {
print $regel;
}
afrit(0);
En hier is een overeenkomstige server. U hoeft zich geen zorgen te maken over een dom netwerk
terminators hier omdat Unix-domeinsockets gegarandeerd op de localhost staan, en
dus alles werkt goed.
#!/usr/bin/perl -Tw
gebruik strikt;
gebruik stopcontact;
gebruik karper;
BEGIN { $ENV{PATH} = "/ Usr / bin:/ bin" }
subspawn; # voorwaartse verklaring
sub logmsg { print "$0 $$: @_ at ", scalaire lokale tijd(), "\n" }
mijn $NAME = "kattensok";
mijn $uaddr = sockaddr_un($NAME);
mijn $proto = getprotobyname("tcp");
socket(Server, PF_UNIX, SOCK_STREAM, 0) || sterven "socket: $!";
ontkoppelen($NAME);
bind (server, $uaddr) || sterven "binden: $!";
luister(Server, SOMAXCONN) || sterf "luister: $!";
logmsg "server gestart op $NAME";
mijn $waitedpid;
gebruik POSIX ":sys_wait_h";
sub MAAIER {
mijn $kind;
terwijl (($waitedpid = waitpid(-1, WNOHANG)) > 0) {
logmsg "geoogst $waitedpid" . ($? ? " met uitgang $?" : "");
}
$SIG{CHLD} = \&REAPER; #hekel aan SysV
}
$SIG{CHLD} = \&REAPER;
voor ($waitedpid = 0;
accepteren(Client, Server) || $wachtpid;
$waitedpid = 0, klant sluiten)
{
volgende als $waitedpid;
logmsg "verbinding op $NAME";
spawn-sub {
print "Hallo daar, het is nu ", scalar localtime(), "\n";
exec("/usr/games/fortune") || die "kan fortuin niet uitvoeren: $!";
};
}
sub-spawn {
mijn $coderef = shift();
tenzij (@_ == 0 && $coderef && ref($coderef) eq "CODE") {
beken "gebruik: spawn CODEREF";
}
mijn $pid;
tenzij (gedefinieerd($pid = vork())) {
logmsg "kan niet forken: $!";
terug te keren;
}
elsif ($pid) {
logmsg "verwekte $pid";
opbrengst; # Ik ben de ouder
}
else {
# Ik ben het kind - ga spawnen
}
open(STDIN, "<&Client") || die "kan client niet naar stdin duppen";
open(STDOUT, ">&Client") || die "kan client niet naar stdout leiden";
## open(STDERR, ">&STDOUT") || die "kan stdout niet naar stderr dup";
exit($coderef->());
}
Zoals je ziet, lijkt het opmerkelijk veel op de TCP-server van het internetdomein
feit dat we verschillende dubbele functies hebben weggelaten--paaien(), logmsg(), ctijd() en
MAAIMACHINE()--die hetzelfde zijn als op de andere server.
Dus waarom zou je ooit een Unix-domeinsocket willen gebruiken in plaats van een eenvoudiger benoemde pipe?
Omdat een benoemde pijp je geen sessies oplevert. Je kunt de gegevens van één proces niet onderscheiden
van een ander. Met socketprogrammering krijgt u voor elke client een aparte sessie; dat is
Waarom aanvaarden() neemt twee argumenten.
Laten we bijvoorbeeld zeggen dat u een langlopende databaseserver-daemon wilt hebben
mensen kunnen er vanaf internet toegang toe krijgen, maar alleen als ze via een CGI-interface gaan.
Je zou een klein, eenvoudig CGI-programma hebben dat alle controles en registraties uitvoert die je voelt
like, en fungeert dan als een Unix-domeinclient en maakt verbinding met uw privéserver.
TCP Klanten met IO::Socket
Voor degenen die de voorkeur geven aan een interface op een hoger niveau dan socketprogrammering, de IO::Socket-module
biedt een objectgeoriënteerde aanpak. Als u om welke reden dan ook deze module mist, kunt u dat doen
Haal gewoon IO::Socket op van CPAN, waar u ook modules vindt die eenvoudige interfaces bieden
naar de volgende systemen: DNS, FTP, Ident (RFC 931), NIS en NISPlus, NNTP, Ping, POP3,
SMTP, SNMP, SSLeay, Telnet en Time - om er maar een paar te noemen.
A Eenvoudig Bedrijf
Hier is een client die een TCP-verbinding tot stand brengt met de "overdag"-service op poort 13 van de
hostnaam "localhost" en drukt alles af wat de server daar wil bieden.
#!/usr/bin/perl -w
gebruik IO::Socket;
$remote = IO::Socket::INET->nieuw(
Proto => "tcp",
PeerAddr => "localhost",
PeerPort => "dag(13)",
)
|| die "kan geen verbinding maken met de dagdienst op localhost";
while (<$remote>) { print }
Wanneer u dit programma uitvoert, zou u iets terug moeten krijgen dat er als volgt uitziet:
Wo 14 mei 08:40:46 MDT 1997
Hier zijn wat deze parameters zijn voor de nieuw() constructor betekent:
"Proto"
Dit is welk protocol je moet gebruiken. In dit geval wordt de sockethandgreep geretourneerd
verbonden met een TCP-socket, omdat we een stream-georiënteerde verbinding willen, dat wil zeggen één
dat gedraagt zich vrijwel als een gewoon oud bestand. Niet alle stopcontacten zijn van dit type.
Het UDP-protocol kan bijvoorbeeld worden gebruikt om een datagramsocket te maken, die wordt gebruikt voor bericht-
passeren.
"PeerAddr"
Dit is de naam of het internetadres van de externe host waarop de server draait. Wij
had een langere naam kunnen specificeren, zoals "www.perl.com", of een adres zoals
"207.171.7.72". Voor demonstratiedoeleinden hebben we de speciale hostnaam gebruikt
"localhost", wat altijd de huidige machine moet betekenen waarop u draait. De
het corresponderende internetadres voor localhost is "127.0.0.1", als u dat liever gebruikt.
"PeerPort"
Dit is de servicenaam of het poortnummer waarmee we verbinding willen maken. Wij hadden kunnen krijgen
weg met het gebruik van alleen "overdag" op systemen met goed geconfigureerde systeemservices
bestand,[VOETNOTA: Het systeemservicesbestand is te vinden in / Etc / services onder Unixy
systems.] maar hier hebben we het poortnummer (13) tussen haakjes opgegeven. Gewoon gebruiken
het getal zou ook hebben gewerkt, maar numerieke letterlijke getallen zorgen ervoor dat programmeurs voorzichtig zijn
nerveus.
Merk op hoe de geretourneerde waarde van de "nieuwe" constructor wordt gebruikt als bestandshandle in de
"herhalingslus? Dat heet een indirect bestandshandle, een scalaire variabele die a bevat
bestandshandvat. Je kunt het op dezelfde manier gebruiken als een normale filehandle. Jij bijvoorbeeld
kan er één regel uit lezen op deze manier:
$regel = <$handle>;
alle overige regels van zijn zo:
@lijnen = <$handle>;
en stuur er op deze manier een regel met gegevens naartoe:
print $handle "enkele gegevens\n";
A Webget Bedrijf
Hier is een eenvoudige client die een externe host nodig heeft om een document op te halen, en vervolgens een lijst
aantal bestanden dat van die host moet worden opgehaald. Dit is een interessantere klant dan de vorige
omdat het eerst iets naar de server stuurt voordat het het antwoord van de server ophaalt.
#!/usr/bin/perl -w
gebruik IO::Socket;
tenzij (@ARGV > 1) { die "gebruik: $0 host-URL ..." }
$host = verschuiving(@ARGV);
$EOL = "\015\012";
$BLANK = $EOL x 2;
voor mijn $document (@ARGV) {
$remote = IO::Socket::INET->new( Proto => "tcp",
PeerAddr => $host,
PeerPort => "http(80)",
) || die "kan geen verbinding maken met httpd op $host";
$op afstand->automatisch spoelen(1);
print $remote "GET $document HTTP/1.0" . $LEG;
terwijl ( <$remote> ) { print }
sluit $op afstand;
}
Er wordt aangenomen dat de webserver die de HTTP-service verwerkt, zich op de standaardpoort, nummer 80, bevindt.
Als de server waarmee u verbinding probeert te maken zich op een andere poort bevindt, zoals 1080 of 8080,
moet dit opgeven als het benoemde parameterpaar, "PeerPort => 8080". De "autoflush"-methode
wordt op de socket gebruikt omdat het systeem anders de uitvoer zou bufferen die we ernaar hebben verzonden.
(Als je een prehistorische Mac gebruikt, moet je ook elke "\n" in je code wijzigen
verzendt gegevens via het netwerk in plaats daarvan als "\015\012".)
Verbinding maken met de server is slechts het eerste deel van het proces: zodra u de
verbinding, moet u de taal van de server gebruiken. Elke server in het netwerk heeft zijn eigen server
weinig commandotaal die het als invoer verwacht. De string die we naar de server sturen
beginnend met "GET" is in HTTP-syntaxis. In dit geval vragen wij eenvoudigweg elke gespecificeerde op
document. Ja, we maken echt voor elk document een nieuwe verbinding, ook al is dat zo
dezelfde gastheer. Dat is de manier waarop je altijd HTTP moest spreken. Recente versies van
webbrowsers kunnen verzoeken dat de externe server de verbinding een tijdje open laat,
maar de server hoeft een dergelijk verzoek niet te honoreren.
Hier is een voorbeeld van het uitvoeren van dat programma, dat we zullen noemen webget:
% website www.perl.com/guanaco.html
HTTP/1.1 404-bestand niet gevonden
Datum: donderdag 08 mei 1997 18:02:32 GMT
Server: Apache/1.2b6
Verbinding: sluiten
Inhoudstype: tekst/html
404 Bestand niet gevonden
Bestand niet gevonden
De opgevraagde URL /guanaco.html is niet gevonden op deze server.
Oké, dus dat is niet erg interessant, omdat het dat specifieke document niet heeft gevonden. Maar
een lang antwoord had niet op deze pagina gepast.
Voor een meer functionele versie van dit programma moet je kijken naar de lwp-verzoek programma
meegeleverd met de LWP-modules van CPAN.
Interactief Bedrijf met IO::Socket
Nou, dat is allemaal prima als je één commando wilt sturen en één antwoord wilt krijgen, maar hoe zit het dan?
iets volledig interactiefs opzetten, ongeveer zoals de manier waarop telnet werken? Op die manier jij
kan een regel typen, het antwoord krijgen, een regel typen, het antwoord krijgen, enz.
Deze client is ingewikkelder dan de twee die we tot nu toe hebben gedaan, maar als je een systeem gebruikt
dat de krachtige "fork" -oproep ondersteunt, is de oplossing niet zo ruw. Zodra je het hebt gemaakt
de verbinding met de dienst waarmee u wilt chatten, bel "fork" om uw bestand te klonen
proces. Elk van deze twee identieke processen heeft een heel eenvoudige taak: de ouder
kopieert alles van het stopcontact tot de standaarduitvoer, terwijl het kind tegelijkertijd
kopieert alles, van standaardinvoer tot de socket. Om hetzelfde te bereiken met behulp van
slechts één proces zou zijn veel moeilijker, omdat het gemakkelijker is om twee processen te coderen om er één te doen
dan het coderen van één proces om twee dingen te doen. (Dit ‘keep-it-simple’-principe a
hoekstenen van de Unix-filosofie, en ook goede software-engineering
waarschijnlijk de reden waarom het zich naar andere systemen verspreidt.)
Hier is de code:
#!/usr/bin/perl -w
gebruik strikt;
gebruik IO::Socket;
mijn ($host, $port, $kidpid, $handle, $line);
tenzij (@ARGV == 2) {die "gebruik: $0 hostpoort" }
($host, $poort) = @ARGV;
# maak een TCP-verbinding met de opgegeven host en poort
$handle = IO::Socket::INET->new(Proto => "tcp",
PeerAddr => $host,
PeerPort => $poort)
|| die "kan geen verbinding maken met poort $port op $host: $!";
$handle->automatisch spoelen(1); # dus de uitvoer komt meteen daar
print STDERR "[Verbonden met $host:$port]\n";
# splitste het programma in twee processen, eeneiige tweelingen
sterven "kan niet splitsen: $!" tenzij gedefinieerd($kidpid = fork());
# het if{}-blok wordt alleen uitgevoerd in het bovenliggende proces
als ($kidpid) {
# kopieer de socket naar standaarduitvoer
while (gedefinieerd ($line = <$handle>)) {
STDOUT $regel afdrukken;
}
kill("TERM", $kidpid); # stuur SIGTERM naar kind
}
# het else{}-blok wordt alleen uitgevoerd in het onderliggende proces
else {
# kopieer standaardinvoer naar de socket
while (gedefinieerd ($line = )) {
print $handle $regel;
}
afrit(0); # voor de zekerheid
}
De "kill" -functie in het "if" -blok van de ouder is er om een signaal naar ons kind te sturen
proces, dat momenteel in het "else" blok draait, zodra de externe server is gesloten
het einde van de verbinding.
Als de externe server gegevens per byte verzendt, en u hebt die gegevens onmiddellijk nodig zonder
wachtend op een nieuwe regel (wat misschien niet gebeurt), wilt u misschien de "while"-lus vervangen
in de ouder met het volgende:
mijn $byte;
terwijl (sysread($handle, $byte, 1) == 1) {
STDOUT $byte afdrukken;
}
Een systeemoproep doen voor elke byte die je wilt lezen is niet erg efficiënt (om het zo te zeggen
mild) maar is het eenvoudigst uit te leggen en werkt redelijk goed.
TCP Servers met IO::Socket
Zoals altijd is het opzetten van een server iets ingewikkelder dan het runnen van een client. De
Het model is dat de server een speciaal soort socket creëert die niets anders doet dan meeluisteren
een bepaalde poort voor inkomende verbindingen. Dit doet zij door te bellen naar de
"IO::Socket::INET->new()"-methode met iets andere argumenten dan de client.
Proto
Dit is welk protocol je moet gebruiken. Net als onze klanten specificeren we hier nog steeds 'tcp'.
Lokale poort
We specificeren een lokale poort in het argument "LocalPort", wat we niet hebben gedaan voor de
cliënt. Dit is de servicenaam of het poortnummer waarvoor u de server wilt zijn.
(Onder Unix zijn poorten onder 1024 beperkt tot de superuser.) In ons voorbeeld zullen we
gebruik poort 9000, maar u kunt elke poort gebruiken die momenteel niet in gebruik is op uw systeem.
Als u een adres probeert te gebruiken dat al in gebruik is, krijgt u het bericht 'Adres is al in gebruik'.
Onder Unix zal het commando "netstat -a" laten zien welke services momenteel servers hebben.
Listen
De parameter "Listen" is ingesteld op het maximale aantal openstaande verbindingen dat we kunnen
accepteren totdat we binnenkomende klanten afwijzen. Zie het als een wachtrij voor wachtende oproepen
jouw telefoon. De low-level Socket-module heeft een speciaal symbool voor het systeem
maximum, namelijk SOMAXCONN.
visfuik
De parameter "Hergebruik" is nodig zodat we onze server handmatig opnieuw kunnen opstarten zonder te wachten
een paar minuten zodat de systeembuffers kunnen worden leeggemaakt.
Zodra de generieke serversocket is gemaakt met behulp van de hierboven genoemde parameters, wordt de
server wacht vervolgens tot een nieuwe client er verbinding mee maakt. De server blokkeert in het "accepteren"
methode, die uiteindelijk een bidirectionele verbinding van de externe client accepteert. (Maken
Zorg ervoor dat u deze hendel automatisch doorspoelt om buffering te omzeilen.)
Om de gebruiksvriendelijkheid te vergroten, vraagt onze server de gebruiker om opdrachten. De meeste servers doen dat niet
doe dit. Vanwege de prompt zonder nieuwe regel, moet u de "sysread" gebruiken
variant van de bovenstaande interactieve client.
Deze server accepteert een van de vijf verschillende opdrachten en stuurt uitvoer terug naar de client.
In tegenstelling tot de meeste netwerkservers verwerkt deze slechts één inkomende client tegelijk.
Multitasking-servers worden behandeld in hoofdstuk 16 van de Kameel.
Hier is de code. Goed
#!/usr/bin/perl -w
gebruik IO::Socket;
gebruik Net::hostent; # voor OOish-versie van gethostbyaddr
$POORT = 9000; # kies iets dat niet in gebruik is
$server = IO::Socket::INET->new( Proto => "tcp",
Lokale Poort => $POORT,
Luister => SOMAXCONN,
Hergebruik => 1);
die "kan de server niet instellen" tenzij $server;
print "[Server $0 accepteert clients]\n";
while ($client = $server->accept()) {
$cliënt->automatisch spoelen(1);
print $client "Welkom bij $0; typ help voor de opdrachtenlijst.\n";
$hostinfo = gethostbyaddr($client->peeraddr);
printf "[Verbinden vanaf %s]\n", $hostinfo ? $hostinfo->naam: $client->peerhost;
print $client "Opdracht?";
while ( <$client>) {
volgende tenzij /\S/; # lege regel
if (/quit|exit/i) { laatste }
elsif (/date|time/i) { printf $client "%s\n", scalaire lokale tijd() }
elsif (/wie/i ) { print $client `wie 2>&1` }
elsif (/cookie/i ) { print $client `/usr/games/fortune 2>&1` }
elsif (/motd/i ) { print $client `cat /etc/motd 2>&1` }
else {
print $client "Opdrachten: stopdatum wie cookie motd\n";
}
} doorgaan met {
print $client "Opdracht?";
}
sluit $client;
}
UDP: Bericht Voorbijgaand
Een ander soort client-server-opstelling is er één waarbij geen verbindingen, maar berichten worden gebruikt. UDP
communicatie brengt veel minder overhead met zich mee, maar biedt ook minder betrouwbaarheid, zoals die er is
geen belofte dat berichten überhaupt zullen aankomen, laat staan in orde en ongeschonden. Nog steeds,
UDP biedt een aantal voordelen ten opzichte van TCP, waaronder de mogelijkheid om ernaar te "uitzenden" of "multicasten".
een hele reeks bestemmingshosts tegelijk (meestal op uw lokale subnet). Als je vind
Maak je te veel zorgen over de betrouwbaarheid en begin controles in je bericht in te bouwen
systeem, dan zou u om te beginnen waarschijnlijk alleen TCP moeten gebruiken.
UDP-datagrammen zijn dat wel niet een bytestream en mag niet als zodanig worden behandeld. Dit maakt gebruik
I/O-mechanismen met interne buffering zoals stdio (bijv afdrukken() en vrienden) vooral
moeizaam. Gebruik systeemschrijven(), of beter sturen(), zoals in het onderstaande voorbeeld.
Hier is een UDP-programma dat lijkt op de eerder gegeven voorbeeld-internet-TCP-client. Echter,
in plaats van één host tegelijk te controleren, zal de UDP-versie er veel controleren
asynchroon door een multicast te simuleren en vervolgens te gebruiken selecteer() om een time-outwachttijd uit te voeren
voor I/O. Om iets soortgelijks met TCP te doen, zou je een andere sockethandle moeten gebruiken
voor elke gastheer.
#!/usr/bin/perl -w
gebruik strikt;
gebruik stopcontact;
gebruik Sys::Hostnaam;
mijn ($count, $hisiaddr, $hispaddr, $histime,
$host, $iaddr, $paddr, $poort, $proto,
$rin, $route, $rtime, $SECS_OF_70_YEARS);
$SECS_OF_70_YEARS = 2_208_988_800;
$iaddr = gethostbyname(hostnaam());
$proto = getprotonaam("udp");
$port = getservbyname("tijd", "udp");
$paddr = sockaddr_in(0, $iaddr); # 0 betekent dat de kernel wordt gekozen
socket(SOCKET, PF_INET, SOCK_DGRAM, $proto) || sterven "socket: $!";
bind(SOCKET, $paddr) || sterven "binden: $!";
$| = 1;
printf "%-12s %8s %s\n", "localhost", 0, scalaire lokale tijd();
$ count = 0;
voor $host (@ARGV) {
$tel++;
$hisiaddr = inet_aton($host) || sterf "onbekende gastheer";
$hispaddr = sockaddr_in($poort, $hisiaddr);
gedefinieerd(verzenden(SOCKET, 0, 0, $hispaddr)) || die "stuur $host: $!";
}
$rin = "";
vec($rin, fileno(SOCKET), 1) = 1;
# time-out na 10.0 seconden
while ($count && select($rout = $rin, undef, undef, 10.0)) {
$rtijd = "";
$hispaddr = recv(SOCKET, $rtime, 4, 0) || sterven "recv: $!";
($poort, $hisiaddr) = sockaddr_in($hispaddr);
$host = gethostbyaddr($hisiaddr, AF_INET);
$histime = uitpakken("N", $rtime) - $SECS_OF_70_YEARS;
printf "%-12s", $host;
printf "%8d %s\n", $histime - time(), scalaire lokale tijd($histime);
$telling--;
}
In dit voorbeeld zijn geen nieuwe pogingen opgenomen, waardoor het mogelijk is dat er geen contact wordt opgenomen met een bereikbaar persoon
gastheer. De meest opvallende reden hiervoor is de congestie van de wachtrijen op de verzendende host
als het aantal hosts waarmee contact moet worden opgenomen voldoende groot is.
sysv IPC
Hoewel System V IPC niet zo veel wordt gebruikt als sockets, heeft het nog steeds een aantal interessante toepassingen.
U kunt SysV IPC of Berkeley echter niet gebruiken mmap() om een variabele gedeeld te hebben
meerdere processen. Dat komt omdat Perl je string opnieuw zou toewijzen als dat niet het geval was
het willen. Je zou de modules "IPC::Shareable" of "threads::shared" kunnen bekijken
dat.
Hier is een klein voorbeeld van gedeeld geheugengebruik.
gebruik IPC::SysV qw(IPC_PRIVATE IPC_RMID S_IRUSR S_IWUSR);
$grootte = 2000;
$id = shmget(IPC_PRIVATE, $grootte, S_IRUSR | S_IWUSR);
gedefinieerd($id) || sterven "shmget: $!";
print "shm-sleutel $id\n";
$bericht = "Bericht #1";
shmwrite($id, $bericht, 0, 60) || sterven "shmwrite: $!";
print "schreef: '$bericht'\n";
shmread($id, $buff, 0, 60) || die "shmread: $!";
print "lees: '$buff'\n";
# de buffer van shmread is voorzien van nul tekens.
substr($buff, index($buff, "\0")) = "";
print "un" tenzij $buff eq $message;
druk "deining\n" af;
print "shm $id\n verwijderen";
shmctl($id, IPC_RMID, 0) || sterven "shmctl: $!";
Hier is een voorbeeld van een semafoor:
gebruik IPC::SysV qw(IPC_CREAT);
$IPC_KEY = 1234;
$id = semget ($IPC_KEY, 10, 0666 | IPC_CREAT);
gedefinieerd($id) || sterven "semget: $!";
druk "sem-id $id\n" af;
Plaats deze code in een apart bestand dat in meer dan één proces kan worden uitgevoerd. Roep het bestand op nemen:
# maak een semafoor
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0, 0);
gedefinieerd($id) || sterven "semget: $!";
$semnum = 0;
$semvlag = 0;
# "neem" semafoor
# wacht tot de semafoor nul is
$semop = 0;
$opstring1 = pack("s!s!s!", $semnum, $semop, $semflag);
# Verhoog het aantal semafoor
$semop = 1;
$opstring2 = pack("s!s!s!", $semnum, $semop, $semflag);
$opstring = $opstring1 . $opstring2;
semop($id, $opstring) || sterven "semop: $!";
Plaats deze code in een apart bestand dat in meer dan één proces kan worden uitgevoerd. Roep dit bestand op geven:
# "geef" de semafoor
# voer dit uit in het oorspronkelijke proces en je zult het zien
# dat het tweede proces doorgaat
$IPC_KEY = 1234;
$id = semget($IPC_KEY, 0, 0);
die tenzij gedefinieerd($id);
$semnum = 0;
$semvlag = 0;
# Verlaag het aantal semaforen
$semop = -1;
$opstring = pack("s!s!s!", $semnum, $semop, $semflag);
semop($id, $opstring) || sterven "semop: $!";
De bovenstaande SysV IPC-code is lang geleden geschreven en ziet er absoluut onhandig uit. Voor een
modernere uitstraling, zie de IPC::SysV-module.
Een klein voorbeeld dat SysV-berichtenwachtrijen demonstreert:
gebruik IPC::SysV qw(IPC_PRIVATE IPC_RMID IPC_CREAT S_IRUSR S_IWUSR);
mijn $id = msgget(IPC_PRIVATE, IPC_CREAT | S_IRUSR | S_IWUSR);
gedefinieerd($id) || die "bericht mislukt: $!";
mijn $verzonden = "bericht";
mijn $type_sent = 1234;
msgsnd($id, pack("l! een*", $type_sent, $sent), 0)
|| die "msgsnd mislukt: $!";
msgrcv($id, mijn $rcvd_buf, 60, 0, 0)
|| die "msgrcv mislukt: $!";
mijn($type_rcvd, $rcvd) = uitpakken("l! a*", $rcvd_buf);
if ($rcvd eq $verzonden) {
druk "oké\n" af;
} Else {
print "niet oké\n";
}
msgctl($id, IPC_RMID, 0) || die "msgctl mislukt: $!\n";
OPMERKINGEN
De meeste van deze routines geven stilletjes maar beleefd "undef" terug als ze falen in plaats van
waardoor uw programma op dat moment doodgaat vanwege een niet-afgevangen uitzondering. (Eigenlijk,
een deel van het nieuwe Stopcontact conversiefuncties wel kwaken() op slechte argumenten.) Dat is dus zo
essentieel om de retourwaarden van deze functies te controleren. Begin altijd met uw socketprogramma's
op deze manier voor optimaal succes, en vergeet niet de -T vlag voor het controleren van vlekken naar de
"#!" lijn voor servers:
#!/usr/bin/perl -Tw
gebruik strikt;
gebruik sigtrap;
gebruik stopcontact;
Gebruik perlipc online met behulp van onworks.net-services