Sockets

accept#

מקבל חיבור נכנס על socket מאזין.

accept הוא צד השרת בלחיצת היד של החיבור. הוא ממתין על GENERICSOCKET — socket שכבר נוצר באמצעות socket, נקשר באמצעות bind, והועבר למצב האזנה באמצעות listen — עד שלקוח מתחבר, ואז מתקין socket מחובר חדש לחלוטין ב־NEWSOCKET ומחזיר את הכתובת הארוזה של הלקוח. GENERICSOCKET נשאר במצב האזנה, מוכן לקריאה הבאה. ההתנהגות משקפת את קריאת המערכת POSIX accept(2).

תקציר#

accept NEWSOCKET, GENERICSOCKET

מה מוחזר#

כתובת הלקוח בצורה הארוזה המשמשת את משפחת הכתובות של GENERICSOCKET — אותה צורה שהיו מעבירים ל־connect או מפרקים באמצעות Socket::unpack_sockaddr_in עבור IPv4, או באמצעות Socket::unpack_sockaddr_in6 / unpack_sockaddr_un עבור IPv6 ו־sockets במרחב Unix בהתאמה. בכישלון מחזיר ערך שקרי (המחרוזת הריקה) וקובע את $! ל־errno הבסיסי.

NEWSOCKET ממולא כתופעת לוואי — הוא ארגומנט filehandle רגיל, לא ערך מוחזר. מילה חשופה כמו CLIENT יוצרת אוטומטית typeglob; filehandle לקסיקלי כמו my $client ממולא במקום:

my $client;
accept($client, $server) or die "accept: $!";
# $client is now a readable/writable handle to the connected peer

מצב גלובלי שהפונקציה נוגעת בו#

  • $! — נקבע בכישלון ל־errno הבסיסי (EINTR, EAGAIN/EWOULDBLOCK על מאזינים לא־חוסמים, ECONNABORTED, EMFILE, ENFILE).

  • $^F — מתאר הקובץ המרבי של המערכת. במערכות התומכות בדגל close-on-exec, accept קובע FD_CLOEXEC על המתאר החדש כאשר מספרו גדול מ־$^F (ברירת מחדל 2, המכסה את הזרמים הסטנדרטיים). יש להעלות את $^F לפני accept אם רוצים שה־socket המקובל ישרוד את exec.

דוגמאות#

לולאת accept מינימלית בסגנון TCP echo:

use Socket;

socket(my $server, PF_INET, SOCK_STREAM, getprotobyname("tcp"))
    or die "socket: $!";
bind($server, sockaddr_in(8080, INADDR_ANY)) or die "bind: $!";
listen($server, SOMAXCONN)                   or die "listen: $!";

while (my $peer = accept(my $client, $server)) {
    my ($port, $iaddr) = sockaddr_in($peer);
    print $client "hello ", inet_ntoa($iaddr), ":$port\n";
    close $client;
}
die "accept: $!";   # loop exits only on error

פירוק הכתובת המוחזרת לצורך רישום ביומן:

use Socket;

my $peer = accept(my $client, $server)
    or die "accept: $!";
my ($port, $iaddr) = sockaddr_in($peer);
printf "connection from %s port %d\n", inet_ntoa($iaddr), $port;

accept לא־חוסם. תחילה יש להעביר את $server למצב לא־חוסם; קריאה ללא חיבור ממתין מחזירה אז ערך שקרי כאשר $! מוגדר כ־EAGAIN או EWOULDBLOCK:

use Errno qw(EAGAIN EWOULDBLOCK);
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);

my $flags = fcntl($server, F_GETFL, 0);
fcntl($server, F_SETFL, $flags | O_NONBLOCK);

my $peer = accept(my $client, $server);
if (!$peer) {
    if ($! == EAGAIN || $! == EWOULDBLOCK) {
        # no pending connection — try again later
    } else {
        die "accept: $!";
    }
}

שימור ה־socket המקובל לאורך exec על ידי העלאת $^F כך שזמן הריצה לא יקבע FD_CLOEXEC:

local $^F = 10_000;
accept(my $client, $server) or die "accept: $!";
exec "/usr/libexec/handler", fileno($client);

מקרי קצה#

  • GENERICSOCKET אינו מאזיןaccept נכשל כאשר $! מוגדר כ־EINVAL. יש לקרוא ל־listen על socket השרת תחילה.

  • NEWSOCKET כבר פתוח — ה־handle הקיים נסגר בשקט לפני שהמתאר החדש מותקן, בדיוק כמו עבור open ו־socket. אם ה־handle הישן היה ההפניה היחידה לזרם עם buffer, כל נתון שלא הוזרם החוצה אובד. יש לסגור במפורש מראש כאשר זה חשוב.

  • אות במהלך הקריאה — אות הנשלח בזמן ש־accept חסום גורם לקריאת המערכת הבסיסית להחזיר EINTR. pperl אינו מבצע auto-restart: accept מחזיר ערך שקרי ומשאיר את $! מוגדר כ־EINTR. יש לעטוף בלולאת ניסיון חוזר אם מטפלי האותות אינם מסיימים את התהליך.

  • מיצוי מתאריםEMFILE (מגבלת תהליך) או ENFILE (מגבלת מערכת) על שרת עמוס היא הסיבה הנפוצה לכך ש־accept מתחיל להיכשל תחת עומס. יש להתייחס אליהם כחולפים; יש לסגור לקוחות בטלים ולנסות שוב.

  • אי־התאמה של משפחת כתובות בערך המוחזר הארוז — ה־buffer הארוז עוקב אחר המשפחה של GENERICSOCKET. פירוק כתובת במרחב Unix באמצעות Socket::unpack_sockaddr_in מפיק זבל. יש לעקוב אחר המשפחה בעצמכם או להשתמש ב־Socket::sockaddr_family לצורך ניתוב.

  • סוגריים הם אופציונליים, פסיקים אינםaccept $c, $s ו־accept($c, $s) שניהם עובדים; accept $c $s הוא שגיאת תחביר.

  • הקשר רשימה לעומת הקשר סקלרי — ערך הכתובת הארוזה המוחזר הוא מחרוזת בתים; הקשר רשימה אינו מרחיב אותו ל־(port, addr). יש לקרוא ל־sockaddr_in / שווה־ערך לפירוק.

הבדלים מן ה-upstream#

תואם מלא ל־Perl 5.42 upstream.

ראו גם#

  • socket — יצירת socket השרת ש־accept ממתין עליו

  • bind — קישור socket השרת לכתובת מקומית לפני listen ו־accept

  • listen — שלב חובה בין bind ל־accept; קובע את עומק ה־backlog

  • connect — המקבילה בצד הלקוח; accept מחזיר את הכתובת שקריאת connect סיפקה

  • getpeername — שחזור אותה כתובת ארוזה מאוחר יותר מ־NEWSOCKET עצמו

  • Socket — הקבועים (PF_INET, SOCK_STREAM, INADDR_ANY) ופונקציות האריזה (sockaddr_in, unpack_sockaddr_in) שהופכים את הערך המוחזר הארוז למשהו שמיש

  • $^F — סף השולט אם accept קובע FD_CLOEXEC על המתאר החדש