סוקטים של UNIX-domain ו־UDP#
לא כל סוקט מגיע על פני רשת. עמוד זה מכסה שני קרובים קלי־משקל יותר של סוקט ה־TCP מהעמוד הקודם: זוג סוקטים של UNIX-domain עבור שני תהליכים קשורים על מכונה אחת, וסוקט UDP עבור datagrams חסרי־חיבור.
שניהם משתמשים באותה משפחת פונקציות מובנות socket ובאותם עוזרי כתובת של Socket. עוטפי ה־IO::Socket::* הגבוהים־רמה אינם זמינים ב־PetaPerl, ולכן המתכונים נשארים בשכבת הפונקציות המובנות.
זוג סוקטים עבור הורה וילד#
socketpair יוצר שני סוקטים מחוברים בקריאה אחת, ללא כתובות, ללא bind, וללא connect. זהו הערוץ האידיאלי בין הורה לילד שהוא עומד לבצע לו fork: דו־כיווני מלא (שני הקצוות יכולים לקרוא ולכתוב), מקומי, וכבר מחובר ברגע שהוא חוזר.
use Socket;
socketpair(my $child_end, my $parent_end, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
or die "socketpair: $!";
my $kid = fork() // die "fork: $!";
if ($kid == 0) {
# Child keeps its end, closes the parent's.
close $parent_end;
$child_end->autoflush(1);
print $child_end "child-says-hi\n";
my $back = <$child_end>;
print "child got: $back"; # child got: ack
exit 0;
}
# Parent keeps its end, closes the child's.
close $child_end;
$parent_end->autoflush(1);
my $msg = <$parent_end>;
print "parent got: $msg"; # parent got: child-says-hi
print $parent_end "ack\n";
waitpid($kid, 0);
מדוע לבחור ב־socketpair על פני pipe רגיל? pipe הוא חד־כיווני - הקורא קורא, הכותב כותב, וזה קבוע בעת היצירה. זוג סוקטים הוא דו־כיווני: כל קצה גם קורא וגם כותב, ולכן חילופי בקשה־ותשובה דורשים קריאת socketpair אחת במקום שני pipes המחווטים בכיוונים מנוגדים.
כלל ה־autoflush מ־TCP חל ללא שינוי: הגדר אותו בכל קצה לפני הכתיבה הראשונה, אחרת בקשה שעברה buffering לעולם אינה מגיעה לצד השני ושני התהליכים נחסמים.
חילופי datagram ב־UDP#
UDP הוא חסר־חיבור. אין connect, אין accept, אין זרם - רק הודעות בדידות, כל אחת ממוענת ליעד וכל אחת משמרת את הגבולות שלה. מקבל מבצע bind לפורט; שולח יורה אליו datagram בעזרת send; המקבל אוסף אותו בעזרת recv, שגם מדווח מי שלח אותו.
use Socket;
# --- receiver on an ephemeral port ---
socket(my $server, PF_INET, SOCK_DGRAM, getprotobyname("udp"))
or die "socket: $!";
bind($server, sockaddr_in(0, INADDR_LOOPBACK)) or die "bind: $!";
my ($port) = sockaddr_in(getsockname($server));
my $kid = fork() // die "fork: $!";
if ($kid == 0) {
# Sender fires one datagram at the receiver's port.
socket(my $client, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or die;
my $to = sockaddr_in($port, INADDR_LOOPBACK);
send($client, "hello-udp", 0, $to) or die "send: $!";
exit 0;
}
# recv fills $buf and returns the sender's packed address.
my $from = recv($server, my $buf, 65535, 0);
my ($sender_port, $sender_ip) = sockaddr_in($from);
print "got '$buf' from ", inet_ntoa($sender_ip), "\n";
# got 'hello-udp' from 127.0.0.1
waitpid($kid, 0);
מה UDP נותן לך ומה לא:
גבולות ההודעות נשמרים.
sendאחד הואrecvאחד.recvמחזיר בדיוק datagram אחד, לעולם לא שניים משורשרים ולעולם לא חצי מאחד. ה־65535הוא האורך המרבי שאתה מוכן לקבל; datagram קצר יותר מחזיר את אורכו האמיתי.אין
connectואין לחיצת יד. השולח אינו פותח חיבור; הוא ממען כל datagram בעזרת הארגומנט הרביעי שלsend.recvמדווח על כתובת השולח כך שתשובה יכולה לחזור באותה דרך.מסירה אינה מובטחת. datagrams של UDP עלולים להישמט, להשתכפל, או לקבל סדר מחדש על־ידי הרשת. על פני ממשק ה־loopback, כמו במתכון זה, זה למעשה לעולם אינו קורה; על פני רשת אמיתית עליך לטפל באובדן בעצמך אם זה משנה.
קישורי הצלבה לעיון#
socketpair- שני סוקטים מקומיים מחובריםsocket- יצירת endpoint של datagram או זרםbind- חיבור המקבל לפורט מקומיsend- ירי datagram אל כתובתrecv- איסוף datagram והשולח שלוgetsockname- גילוי הפורט הקשור
הבא#
עבור זיכרון משותף וסמפורים - ערוצים שהקרנל הוא הבעלים שלהם ולא זרמים בין תהליכים - ראה System V IPC.