סוקטים של 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, כמו במתכון זה, זה למעשה לעולם אינו קורה; על פני רשת אמיתית עליך לטפל באובדן בעצמך אם זה משנה.

הבא#

עבור זיכרון משותף וסמפורים - ערוצים שהקרנל הוא הבעלים שלהם ולא זרמים בין תהליכים - ראה System V IPC.