סוקטים

connect#

יוזם חיבור מסוקט לכתובת מרוחקת.

connect הוא החצי בצד הלקוח של לחיצת היד של הסוקט. בהינתן SOCKET שנוצר עם socket וכתובת ארוזה NAME בצורה שמשפחת הכתובות של הסוקט מצפה לה, הוא מבקש מהקרנל להקים חיבור לאותו עמית. זוהי עטיפה דקה מעל קריאת המערכת connect(2) ויורשת כל אחת מהסמנטיקות של אותה קריאה - חוסם, לא־חוסם, ושגיאה.

תקציר#

connect SOCKET, NAME

מה מקבלים בחזרה#

אמיתי (1) בהצלחה, שקרי בכישלון עם $! מוגדר לשגיאת המערכת. תמיד יש לבדוק את ערך ההחזרה - אין התנהגות שימושית של ברירת מחדל כאשר ניסיון חיבור נכשל:

connect($sock, $peer)
    or die "connect to $host:$port failed: $!";

מצב גלובלי שהוא נוגע בו#

  • $! נקבע ל־errno של הקרנל בכישלון. יש לפרש אותו כנגד הקבועים של Errno, לא לפי ערך המחרוזת - ההודעה לאחר המרה למחרוזת תלוית locale.

  • מטפל הסוקט עצמו נושא את מצב החיבור; אף משתנה מיוחד אחר לא מעורב.

בניית NAME: זה struct ארוז, לא מחרוזת#

הארגומנט השני אינו מחרוזת "host:port". זהו sockaddr ארוז במבנה הבתים המדויק שהקרנל מצפה לו עבור משפחת הכתובות של הסוקט. יש לבנותו עם העזרים מ־Socket:

  • sockaddr_in($port, $inaddr) עבור IPv4 - $inaddr מ־inet_aton או inet_pton.

  • sockaddr_in6($port, $in6addr) עבור IPv6 - $in6addr מ־inet_pton(AF_INET6, ...).

  • sockaddr_un($path) עבור סוקטי תחום־Unix.

אריזה ידנית עם pack עובדת, אך אין סיבה לעשות זאת כש־Socket כבר מייצא את הבוני הנכונים.

דוגמאות#

לקוח TCP IPv4 פשוט:

use Socket;

socket(my $sock, PF_INET, SOCK_STREAM, getprotobyname("tcp"))
    or die "socket: $!";

my $addr = sockaddr_in(80, inet_aton("93.184.216.34"));
connect($sock, $addr)
    or die "connect: $!";

print $sock "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";

לקוח מודרני אגנוסטי למשפחת־כתובות באמצעות getaddrinfo:

use Socket qw(getaddrinfo SOCK_STREAM);

my ($err, @res) = getaddrinfo("example.com", "80",
                              { socktype => SOCK_STREAM });
die "getaddrinfo: $err" if $err;

my $sock;
for my $ai (@res) {
    socket($sock, $ai->{family}, $ai->{socktype}, $ai->{protocol})
        or next;
    last if connect($sock, $ai->{addr});
    close $sock;
    undef $sock;
}
defined $sock or die "could not connect to any address";

לקוח תחום־Unix:

use Socket;

socket(my $sock, PF_UNIX, SOCK_STREAM, 0) or die "socket: $!";
connect($sock, sockaddr_un("/tmp/my.sock"))
    or die "connect: $!";

connect לא־חוסם עם זמן־פסק באמצעות select - הדרך הניבית להגביל ניסיון connect ב־Perl טהור:

use Socket;
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
use Errno qw(EINPROGRESS);

socket(my $sock, PF_INET, SOCK_STREAM, 0) or die "socket: $!";
my $flags = fcntl($sock, F_GETFL, 0);
fcntl($sock, F_SETFL, $flags | O_NONBLOCK);

my $ok = connect($sock, sockaddr_in(80, inet_aton("93.184.216.34")));
if (!$ok && $! == EINPROGRESS) {
    my $w = '';
    vec($w, fileno($sock), 1) = 1;
    my $n = select(undef, $w, undef, 5.0);   # 5-second timeout
    $n > 0 or die "connect timed out";
    # Check SO_ERROR to see whether the async connect succeeded:
    my $err = unpack("i", getsockopt($sock, SOL_SOCKET, SO_ERROR));
    $err == 0 or die "connect: " . do { local $! = $err; "$!" };
}

מקרי קצה#

  • אי־התאמת משפחת־כתובות. אם NAME נארז עבור משפחה שונה מזו שבה הסוקט נוצר (לדוגמה sockaddr_in כנגד סוקט PF_INET6), connect נכשל עם EAFNOSUPPORT או EINVAL. הבייטים הארוזים נושאים את המשפחה; הקרנל בודק.

  • כבר מחובר. קריאה ל־connect על סוקט זרם שכבר מחובר נכשלת עם EISCONN.

  • סוקטים נטולי חיבור. על סוקטי SOCK_DGRAM / SOCK_RAW, connect לא מחליף חבילות; הוא רושם עמית ברירת מחדל כדי שקריאות send / recv עוקבות יוכלו להשמיט את הכתובת. ניתן לקרוא לו שוב עם NAME חדש, או לאפסו על־ידי קריאה עם sockaddr של AF_UNSPEC.

  • סוקטים לא־חוסמים. אם הסוקט במצב לא־חוסם, connect של TCP בדרך־כלל מחזיר שקר עם $! מוגדר ל־EINPROGRESS. החיבור עדיין לא הוקם. יש להמתין שהסוקט יהפוך לזמין לכתיבה (באמצעות select או לולאת האירועים שלכם), אחר־כך לקרוא את SO_ERROR עם getsockopt כדי לקבל את המצב הסופי. SO_ERROR בערך אפס משמעו שהחיבור עומד; כל דבר אחר הוא קוד הכישלון האמיתי.

  • אותות הקוטעים את הקריאה. connect חוסם יכול להחזיר שקר עם $! מוגדר ל־EINTR אם מטפל אות רץ. הסוקט עדיין מתחבר ברקע; יש להמתין לזמינות לכתיבה ולבדוק את SO_ERROR כמו במקרה הלא־חוסם - אין לנסות שוב את הקריאה ל־connect, זה מניב EALREADY.

  • כתובת מקור לא־מאוגדת. אם bind לא נקרא קודם, הקרנל בוחר כתובת מקור ופורט זמני אוטומטית. המקרה השכיח הוא לדלג על bind בצד הלקוח.

  • סוקט סגור או שלא נפתח. נכשל עם EBADF.

הבדלים מ־upstream#

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

ראו גם#

  • socket - יוצר את ה־endpoint ש־connect פועל עליו

  • bind - מקבע את הכתובת המקומית לפני החיבור; נדרש לעיתים נדירות בלקוח

  • accept - המקבילה בצד השרת של connect

  • getpeername - מחזיר את כתובת העמית של סוקט מחובר

  • shutdown - סוגר כיוון אחד של סוקט מחובר מבלי לשחרר את ה־descriptor

  • Socket - אורזים (sockaddr_in, sockaddr_un, …) וקבועים (PF_INET, SOCK_STREAM, …) המשמשים לבניית ארגומנטים

  • Errno - קבועים סימבוליים להשוואת $! כנגד EINPROGRESS, EISCONN, ECONNREFUSED, …