המדריך של ביג' לתכנות רשת
הקודםהבא

4. קריאות מערכת

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

הדבר הכי הרבה נתקעים בו אנשים הוא באיזה סדר לקרוא לדברם האלה. בזאת עמודי המדריך (man pages) לא עוזרים, כמו שבטח גילית. טוב, כדי לעזור למצב הנורא הזה, אני ניסיתי להציג את קריאות המערכת בדיוק (בערך) בסדר שבו אתם תצטרכו לקרוא להן בתוכנית שלכם.

זה, בנוסף לדוגמאות קוד פה ושם, קצת חלב ועוגיות (שאני חושש שתצטרכו לספק לעצמכם), וקצת אומץ, אתם תשגרו מידע באינטרנט בלי בעיה!

4.1. socket()--השג את מתאר הקובץ!

אני חושב שאני לא יכול לדחות זאת עוד -- אני חייב לדבר על קריאת המערכת socket() . הנה פירוט:


    #include <sys/types.h>
    #include <sys/socket.h>

    int socket(int domain, int type, int protocol); 

אבל מה הם הארגומנטים האלה? קודם, domain צריך להיות "AF_INET", בדיוק כמו struct sockaddr_in (לעיל.) , הארגומנט הבא type אומר לקרנל איזה סוג של שקע זה: SOCK_STREAM או SOCK_DGRAM. לבסוף, כוונו את protocol to "0" כדי ש socket() תבחר את הפרוטוקול המתאים, בהתאם ל type. (הערות: יש הרבה יותר דומיינים (domains) ממה שרשמתי כאן.יש הרבה יותר סוגים (types) ממה שרשמתי כאן. ראה בעומד במדריך של socket() . בנוסף , יש דרך "טובה יותר" להשיג את הפרוטוקול (protocol). ראה עמוד המדריך של getprotobyname() .)

הפונקציה socket() פשוט מחזירה מתאר שקע (socket descriptor) שאתה יכול להשתמש בו אחר-כך בקריאות מערכת אחרות, או -1 במקרה של שגיאה. במשתנה הגלובלי errno יושם מספר השגיאה (ראה את עמוד המדריך של perror() .)

בחלק מהתיעוד, אתה תראה אזכור של "PF_INET" מיסטי כלשהוא. חיה מוזרה זו בקושי נראת בטבע, אבל כדאי שאבהיר זאת כאן. פעם, לפני זמן רב, חשבו שאולי משפחת הכתובות (address family) (מה ש "AF" ב "AF_INET" אומר) אולי יתמכו בכמה פרוטוקולים שהתייחסו אליהם ע"י משפחת הפרוטוקולים (protocol family) שלהם (מהש "PF" ב "PF_INET" אומר, זה לא קרה. אז הדבר הנכון לעשות הוא להשתמש ב AF_INET ב struct sockaddr_in שלך וב PF_INET כשאתה קורא ל socket(). אבל באופן מעשי אתה יכולה להשתמש ב AF_INET בכל מקום. ומכיוון שזה מה ש ו.ריצארד סטיבנס (W. Richard Stevens) עושה בספרו, זה מה שאני אעשה כאן.

בסדר,בסדר,בסדר אבל השקע הזה טוב? התשובה היא שלא ממש טוב לבדו, ואתה צריך לקרוא קדימה, ולבצע עוד קריאות מערכת עליו, כדי שיהיה הגיון.

4.2. bind()--על איזה פתחה (port) אני נצמא?

ברגע שיש לך שקע, אתה אולי תצטרך לשייך לשקע זה פתחה (port) במחשב שלך. ( זה שכיח כאשר אתה מתכוון להאזין בעזרת listen() לחיבורים באים בפתחה ספציפית -- מה ש"צינוק רב מששתפים" עושה שהוא אומר לך לבצע "telnet to x.y.z port 6969") הגרעין משתמש במספר פתחה כדי לתאם בין חבילת מידע נכנסת, למתאר שקע של תהליך מסוים. אם אתה מתכוון לבצע רק התחברות (עם connect()), קטע אולי יהיה לא הכרחי. קרא אותו בכל מקרה, רק בישביל הקטע.

הנה התמצית בישביל קריאת מערכתbind():


    #include <sys/types.h>
    #include <sys/socket.h>

    int bind(int sockfd, struct sockaddr *my_addr, int addrlen); 

sockfd הוא מתאר קובץ השקע שהוחזר מ socket(). my_addr הוא מצביע לstruct sockaddr שמכיל מידע על הכתבת שלך, כלומר, הפתחה ו כתובת ה IP. addrlen יכול להיות מכוון ל sizeof(struct sockaddr).

ואוו. זה הרבה לספוג בחתיכה אחת. הנה דוגמא:


    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>

    #define MYPORT 3490

    main()
    {
        int sockfd;
        struct sockaddr_in my_addr;

        sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!

        my_addr.sin_family = AF_INET;         // host byte order
        my_addr.sin_port = htons(MYPORT);     // short, network byte order
        my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
        memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct

        // don't forget your error checking for bind():
        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
        .
        .
        . 

יש כאן כמה דברים לשים לב אליהם: my_addr.sin_port הוא בסדר הבתים של הרשת. כמו גם my_addr.sin_addr.s_addr. . דבר אחר לשים לב אליו הוא שקבצי .h ( ה header files) עלולים להשתנות ממערכת למערכת. כדי להיות בטוח בדוק את עמודי המדריך המקומיים שלך (man pages.).

נושא אחרון על bind(), אני צריך להזכיר שחלק מהתהליך לקבל את כתובת ה IP של עצמך ו/או מספר הפתחה יכול להיות אוטומטי:


        my_addr.sin_port = 0; // choose an unused port at random
        my_addr.sin_addr.s_addr = INADDR_ANY;  // use my IP address 

ע"י השמת אפס ב my_addr.sin_port , אתה אומר ל bind() לבחור פורט בשבילך. באותו אופן, ע"י השמה ב my_addr.sin_addr.s_addr את INADDR_ANY, אותה אומר לה למלא אוטומטית את כתובת ה IP של המחשב שהתהליך רץ עליו.

אם אתה שם לב לדברים קטנים, אתה אולי הייתה שם לך שלא שמתי את INADDR_ANY בסדר הבתים של הרשת! אבל לי יש מידע פנימי INADDR_ANY הוא בעצם אפס! לפאס יש אפס על הסיביות אפילו אם אתה משנה את סדר הבתים. בכל אופן יכול להיות מימד מקביל שבו INADDR_ANY הוא, נגיד, 12, ואז הקוד הזה לא יעבוד שם. זה בסדר איתי:


        my_addr.sin_port = htons(0); // choose an unused port at random
        my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // use my IP address

עכשיו אתה כל כך נייד שאתה לא תאמין עד כמה. אני רק רציתי לציין זאת, מכיוון שרב הקוד שתראה לא יטרח לשים את INADDR_ANY דרך htonl().

bind() גם מחזירה -1 כשיש שגיאה, ושמה ב errno את מספר השגיאה.

דבר אחר לשים לב שאתה קורא ל bind(): אל תלך למטה מידי עם מספרי הפתחות. כל הפתחות מתחת ל 1024 הן שמורות (אלא אם כן אתה משתמש על)! אתה יכול לקבל כל מספר פתחה מעל זה, עד 65535 (כל עוד הן לא בשימוש כבר ע"י תוכנה אחרת.)

לפעמים, אתה עלול לשים לב, שאתה מנסה להרית שוב שרת ו bind() נכשלת בטענה של "כתובת כבר בשימוש" ("Adress already in use"). מה זה אומר? טוב, חלק מהשקע שהתחברו אליו עדיין נמצא בגרעין, והוא תופס את הפתחה. אתה יכול לחכות עד שהוא יתפנה (דקה בערך) , או להוסיף קוד לתוכנה שלך כדי לאפשר לך שימוש חוזר בפתחה, ככה:


    int yes=1;
	//char yes='1'; // Solaris people use this

    // lose the pesky "Address already in use" error message
    if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) 

    {
        perror("setsockopt");
        exit(1);
    } 

דבר קטן נוסף בקשר ל bind(): יש פעמים שאתה לא חייב לקרוא לה. אם אתה מתחבר (בעזרת connect()) ולא אכפת לך מאיזה פתחה מקומית אתה משתמש (כמו במקרה של telnet שרק אכפת לך מהפתחה המרוחקת), אתה יכול פשוט לקרוא ל connect(), היא תבדוק אם השקע לא משוייך(unbound), ותשייך אותו (עם bind()) לפתחה לא משומשת אם נדרש.

4.3. connect()--היי, אתה!

בוא נעמיד פנים לרגע שאתה תוכנת טלנט. המשתמש שלך מצווה עליך (כמו בסרט TRON) להשיג מתאר קובץ שקע. אתה מקשיב וקורא ל socket(). אחר כך הוא אומר לך להתחבר ל "10.12.110.57" בפתחה "23" (הפורט הסטנדרטי של טלנט.) יוו! מה אתה עושה עכשיו?

למזלך, התוכנית, אתה עכשיו מחפש את החלק על connect()--איך להתחבר למארח מרוחק. אז קרא קדימה! אין זמן להפסיד!

קריאת המערכת connect() היא ככתוב מטה:


    #include <sys/types.h>
    #include <sys/socket.h>

    int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

כש sockfd הוא מתאר קובץ השקע הידידותי שלנו, כפי שהוחזר מ socket() , ו serv_addr הוא struct sockaddr מכיל את כתובת ה IP והפורט של היעד, ו addrlen יכול להיות מכוון ל sizeof(struct sockaddr).

זה לא מתחיל לעשות יותר הגיון? הנה דוגמא:


    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>

    #define DEST_IP   "10.12.110.57"
    #define DEST_PORT 23

    main()
    {
        int sockfd;
        struct sockaddr_in dest_addr;   // will hold the destination addr

        sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!

        dest_addr.sin_family = AF_INET;          // host byte order
        dest_addr.sin_port = htons(DEST_PORT);   // short, network byte order
        dest_addr.sin_addr.s_addr = inet_addr(DEST_IP);
        memset(&(dest_addr.sin_zero), '\0', 8);  // zero the rest of the struct

        // don't forget to error check the connect()!
        connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
        .
        .
        .

שוב, בדוק את ערך ההחזר של connect()--היא תחזיר -1 במקרה של שגיאה ותשים את ערך השגיאה במשתנה errno.

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

4.4. listen()--מישהו מוכן להתקשר אלי?

טוב, זמן לשינוי הקצב. מה אם אתה לא רוצה להתחבר למארח מרוחק. נאמר, רק בישביל הקטע, שאתה רוצה לחכות לחיבורים נכנסים ונהל אותם בצורה כלשהיא. לתהליך זה 2 צעדים: קודם את מאזין (עם listen()), ואז מקבל את החיבור (עם accept() ראה למטה.)

קריאת המערכת listen היא די פשוטה, אבל דרוש קצת הסבר:


    int listen(int sockfd, int backlog);

sockfd , הוא מתאר קובץ השקע הרגיל מקריאת המערכת socket(). backlog is the number of connections allowed on the incoming queue. What does that mean? Well, incoming connections are going to wait in this queue until you accept() הוא המספר של חיבורים המורשים בתור של הנכנסים. מה זה אומר? טוב, חיבורים נכנסים הולכים לחכות בתור הזה עד שתעשה להם accept() (תקבל את החיבור - ראה למטה) וזה הגבול על כמה יכולים להיות בתור הזה. רוב המערכות מגבילות באופן שקט את מספר זה ל 20; אתה בטח יכול להחלץ עם כיוונו ל 5 או 10.

שוב, כרגיל, listen() מחזירה -1 ושמה את מספר השגיאה ב errno במקרה שגיאה.

טוב, אתם בטח יכולים לדמיין שאני צריכים לקרוא ל bind() לפני שאנו קוראים ל listen() או שגרעין יגרום לנו להאזין לפתחה אקראית. אז אם אתה מתכנן להקשיב לחיבורים נכנסים , סדר קריאות המערכת הוא:


    socket();
    bind();
    listen();
    /* accept() goes here */

אני פשוט אשיר את זה במקום קוד דוגמא, מכיוון שזה די מסביר את עצמו. (הקוד בחלק על accept() למטה, הוא יותר שלם) החלק המתוסבך של כל הקטע הזה הוא הקריאה ל accept().

4.5. accept()--"תודה שהתקשרת לפתחה 3490."

היו מוכנים--קריאת המערכת accept() היא קצת מוזרה! מה שהולך לקרות זה ככה: מישהו רחוק רחוק ינסה להתחבר (עם connect())למחשב שלכם ולפתחה שאתם מאזינים לה (עם listen()) החיבור יכנס לתור מחכה להתקבל (עם accept()). אתה קורא ל accept() ואומר לה לקבל את החיבור שמחכה. היא תחזיר לך מתאר קובץ שקע חדש לגמרי להשתמש בו עבור החיבור היחיד הזה! זה נכון, פתאום יש לך שני מתארי קובץ שקע במחיר אחד! המקורי עדיין מאזין לפתחה, והשקע החדש שנוצר מוכן סוף סוף מוכן לשליחה (send()) וקבלה recv(). אנחנו שם!

קריאת המערכת היא ככה:


     #include <sys/socket.h>

     int accept(int sockfd, void *addr, int *addrlen);

sockfd הוא מתאר קובץ השקע שמאזינים לו (עם listen()) . קל מספיק. addr בדרך כלל יהיה מצביע ל struct sockaddr_in. מקומי. זה איפה שהמידע על החיבור הנכנס יהיה (ואיתו אתם יכולים לדעת איזה מארח קורא לכם ומאיזה פתחה). addrlen הוא משתנה שלם מקומי שצריך להיות מכוון ל sizeof(struct sockaddr_in) לפני שהוא מגיע ל accept(). Accept לא תשים יותר יותר בתים מהמציון בו לתוך addr. אם היא תשים פחות, היא תשנה את ערךaddrlen to בהתאם.

נחשו מה? accept() תחזיר -1 ותשנה את errno במקרה של שגיאה. מתערב שלא הבנתם את זה.

כמו קודם זה די הרבה כדי להבין בפעם אחת, אז הנה דוגמת קוד להבנתך:


    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>

    #define MYPORT 3490    // the port users will be connecting to

    #define BACKLOG 10     // how many pending connections queue will hold

    main()
    {
        int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
        struct sockaddr_in my_addr;    // my address information
        struct sockaddr_in their_addr; // connector's address information
        int sin_size;

        sockfd = socket(AF_INET, SOCK_STREAM, 0); // do some error checking!

        my_addr.sin_family = AF_INET;         // host byte order
        my_addr.sin_port = htons(MYPORT);     // short, network byte order
        my_addr.sin_addr.s_addr = INADDR_ANY; // auto-fill with my IP
        memset(&(my_addr.sin_zero), '\0', 8); // zero the rest of the struct

        // don't forget your error checking for these calls:
        bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

        listen(sockfd, BACKLOG);

        sin_size = sizeof(struct sockaddr_in);
        new_fd = accept(sockfd, (struct sockaddr *)&their_addr, 
&sin_size);
        .
        .
        . 

שוב, שים לב שאנחנו נשתנש במתאר השקע new_fd לכל הקריאות לשליחה וקבלה (send() ו recv()בהתאמה.) . אם אתה מקבל רק חיבור אחד , אתה יכול לסגור (עם close()) את השקע שמאזין sockfd כדי למנוע חיבורים נוספים לאותה פתחה, אם אתה חפץ בכך.

4.6. send() ו recv()--ו....דברו איתי!

שתי פונקציות אלה הן לשימוש מעל "שקעים זורמים" או "שקעי חבילה" עם חיבור. אם אתה רוצה להשתמש בחבילות מידע רגילות ולא מחוברות, עליך לקרוא את החלק על sendto() ו recvfrom(), למטה.

קריאת המערכת send() :


    int send(int sockfd, const void *msg, int len, int flags);

sockfd הוא מתאר השקע שאתה רוצה לשלוח אליו מידע (אם זה זה שהוחזר מ socket() או זהשהשגת מ accept().) msg הוא מצביע למידע שאתה רוצה לשלוח, ו lenהוא אורך המידע בבתים. וב flags פשוט שים 0. ((ראה את עמוד המדריך של send() לעוד מידע בקשר לדגלים.)

קוד לדוגמא יכול להיות זה:


    char *msg = "Beej was here!";
    int len, bytes_sent;
    .
    .
    len = strlen(msg);
    bytes_sent = send(sockfd, msg, len, 0);
    .
    .
    .

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

קריאת המערכת recv() דומה לה :


    int recv(int sockfd, void *buf, int len, unsigned int flags);

sockfd הוא מתאר השקע שממנו אני רוצה לקרוא מידע, buf הוא המקום במזכרון שאליו יוכנס המידע, len הוא האורך המירבי של המקום בזכרון, ושוב ב flags אתה יכול לשים 0. (ראה את עמוד המדריך של recv() עבוד מידע על דגלים.)

recv() מחזירה את מספר הבתים שבאמת נכתבו לתוך buffer, או -1 במקרה של שגיאה (וב errno מושם ערך השגיאה,בהתאם).

חכו! recv() יכולה להחזיר 0. זה אומר רק דבר אחד: הצד המרוחק סגר את החיבור! ערך החזר 0 זו הדרך של recv()לידע אותך שזה קרה.

הנה, זה היה קל לא? עכשיו אתה יכול להעביר מידע קדימה ואחורה ב"שקעים זורמים"! אתה מתכנת רשת ביוניקס!

4.7. sendto() ו recvfrom()--ודברו איתי, בסגנון חבילות (DGRAM)

"כל זה יפה ונחמד", אני שומע אותך אומר, "אבל איפה זה משאיר אותי עם שקעים ללא חיבור?", אין בעיה! יש לנו בדיוק את הדבר הדרוש.

מכיוון ש"שקעי חבילה" למחוברים למארח מרוחק. נחש איזו חתיכת מידע אנו צריכים לפני שאנו שולחים את המידע? נכון! את כתובת היעד! הנה הסקופ:


    int sendto(int sockfd, const void *msg, int len, unsigned int flags,
               const struct sockaddr *to, int tolen);

כמו שאתה רואה, דריאת מערכת זו היא כמו הקראה ל send()עם הוספה של שני ארגומנטים חדשים. to הוא מצביע ל struct sockaddr (לך בטח יהיה struct sockaddr_in ותבצע casting ברגע האחרון) שמכיל את כתובת ה IP של היעד ומספר פתחה. tolen can יכול להיות פשוט מכוון ל sizeof(struct sockaddr).

כמו send(), sendto() מחזירה את מספר הבתים שבאמת נשלחו ( ששוב, יכול להיות פחות המהמספר הבתים שאמרת לה לשלוח!) או -1 במקרה של שגיאה.

באותו אופן דומות recv() ו recvfrom(). התחביר של recvfrom() הוא:


    int recvfrom(int sockfd, void *buf, int len, unsigned int flags, 
               struct sockaddr *from, int *fromlen);

שוב, כמו recv() עם הוספה של כמה אגומנטים. from הוא מצביע ל struct sockaddr מקומי, שימולא עם כתובת ה IP והפתחה של השולח. fromlen הוא מצביע למשתנה מקומי שלם (int) שצריך להיות מאותחל ל sizeof(struct sockaddr). כשהפונקציה תחזור, הארגומנט fromlen יכיל את האורך האמיתי של הכתובת שמאוכסנת ב from.

recvfrom() מחזירה את מספר הבתים שהתקבלו או -1 במקרה של שגיאה (שכ errno מכוון בהתאמה.)

זכור, אם אתה משתמש ב connect() ל"שקעי חבילה", אתה יכול פשוט להשתמש ב send() ו recv() לכל ההעברות. השקע עצמו הוא עדיין "שקע חבילה" ו חבילות המידע עדיין ישתמשו ב UDP , אבל ממשק השקעים יוסיף אוטומטית את המידע על היעד והמקור בשבילך.

4.8. close() ו shutdown()--עופו לי מהפרצוף!

ואו! אתה שלחת וקיבלת (עםsend() ו recv()) מידע כל היום, ונמאס לך. אתה מוכן לסגור את החיבור על מתאר השקע שלך. זה קל. אתה פשוט יכול להשתמש בפנקציה הרגילה של יוניקס לסגור מתארי קובץ close() :


    close(sockfd);

זה ימנע קריאה וכתיבה נוספת לשקע. מישהו שינסה לקרוא או לכתוב לשקע בצד המרוחק יקבל שגיאה.

רק במקרה שאתה תרצה שליטה יותר טובה על איך שקעים נסגרים, אתה יכול להשתמש בפונקצייה shutdown() . היא נותנת לך חסגור את ההתקשרות בכיוון מסוים, או בשניהם (כמו ש close() עושה.) תחביר :


    int shutdown(int sockfd, int how);

sockfd הוא מתאר קובץ השקע שאתה רוצה לכבות, ו how הוא אחד מהבאים:

shutdown() תחזיר 0 בהצלחה, ו -1 במקרה של שגיאה, (כש errno מכוון בהתאם.)

אם אתה מתכנן להשתמש ב shutdown() על שקעים ללא חיבור, זה פשוט יעשה את השקע לא זמין לפונקציות send() ו recv() (זכור שאתה יכול להשתמש בהן אם אתה משתמש ב connect() על שקע ללא החיבור שלך.)

חשוב לציין ש shutdown() לא באמת סוגרת את מתאר השקע -- היא רק משנה את השמישות שלו. כדי לשחרר מתאר שקע, עליך להשתמש ב close().

אין בעיות.

4.9. getpeername()--מי אתה ?

הפונקציה הזאת קלה.

כל כך קלה שכמעט לא נתתי לה חלק משלה. אבל הנה הוא בכל מקרה.

הפונקציה getpeername() תגיד לך מי נמצא בצד השני של "שקע זורם" מחובר . התחביר שלה:


    #include <sys/socket.h>

    int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

sockfd sockfd הוא מתאר הקובץ של ה"שקע הזורם" המחובר, addr הוא מצביע ל struct sockaddr (או ל struct sockaddr_in) שיחזיק את המידע על הצד השני של החיבור, ו addrlen הוא מצביע ל int, וצריך להיות מאותחל ל sizeof(struct sockaddr).

הפונקציה מחזירה -1 בשגיאה , ושמה ב errno את ערכה בהתאמה.

ברגע שיש לך את הכתובת אתה יכול להשתמש ב inet_ntoa() או ב gethostbyaddr() כדי להדפיס או להשיג עליה עוד מידע. לא, אתה לא יכול להשיג את שם הכניסה שלהם למערכת. (טוב, בסדר, אם המחשב האחר מריץ ident daemon, זה אפשרי. אבל זה מחוץ לתחום של מסמך זה. תבדקו את המסמך האינפורמטיביout RFC-1413 לעוד מידע.)

4.10. gethostname()--מי אני?

אפילו יותר קלה מ getpeername() היא הפונקציה gethostname(). היא תחזיר את שם המחשב שהתוכנית שלך רצה עליו. בשם יכולה להשתמש בפוקציה gethostbyname(), שנמצאת מטה, כדי לקבוע את ה IP של המחשב שלך.

מה יכול להיות יותר כיף? אני יכול לחשוב על כמה דברים, אבל הם לא קשורים לתכנות בשקעים. בכל מקרה , הנה זה:


    #include <unistd.h>

    int gethostname(char *hostname, size_t size);

הארגומנטים הם פשוטים: hostname is a הוא מצביע למערך של תווים (chars)שיכיל את שם המחשב כשהפונקציה תחזור, ו size size זה הגודל בבתים של המערך hostname.

הפונקציה תחזר0 בהצלחה, ו -1 בשגיאה, מכוונת את errno כרגיל.

4.11. DNS--אתה אומר "whithouse.gov", אני אומר "198.137.240.92"

המקרה שאתה לא יודע מה זה DNS, זה רשאי תיבות של "Domain Name Service". בקיצור , זה אתה אומר לו מה היא הכתובת קריאת-אדם של אתר, הוא יתן לך את כתובת ה IP (ככה שתוכל להשתמש בה ב bind(), connect(), sendto(), או כל דבר אחר שאתה זוקוק לכתובת בישבילו) בדרך זו שמישהו נכנס כך:


    $ telnet whitehouse.gov

telnet יכולה לגלות שהיא צריכה להתחבר (עם connect()) ל "198.137.240.92".

אבל איך זה עובד? אתה תשתמש בפונקציה gethostbyname():


    #include <netdb.h>

    struct hostent *gethostbyname(const char *name);

כמו שאתה רואה, היא מחזירה מצביע ל struct hostent, שנראה כך:


    struct hostent {
        char    *h_name;
        char    **h_aliases;
        int     h_addrtype;
        int     h_length;
        char    **h_addr_list;
    };
    #define h_addr h_addr_list[0]

הנה תיאורים של השדות בstruct hostent:

gethostbyname() תחזיר מצביע ל struct hostentממולא, או NULL בשגיאה (אבל ב errno לא מושם ערך השגיאה, אלא ב--h_errno במקום. ראה herror(), למטה.)

אבל איך משתמשים בזה?לפעמים (שכאנחנו מכלים מקריאה של מדריכי מחשבים), רק הצגה של המידע על הקורא לא מספקת. הפונקציה הזאת היא ודאוי יותר קלה לשימוש,מלהצגה.

הנה תוכנית לדוגמא::


    /*
    ** getip.c -- a hostname lookup demo
    */

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>

    int main(int argc, char *argv[])
    {
        struct hostent *h;

        if (argc != 2) {  // error check the command line
            fprintf(stderr,"usage: getip address\n");
            exit(1);
        }

        if ((h=gethostbyname(argv[1])) == NULL) {  // get the host info
            herror("gethostbyname");
            exit(1);
        }

        printf("Host name  : %s\n", h->h_name);
        printf("IP Address : %s\n", inet_ntoa(*((struct in_addr *)h->h_addr)));
       
       return 0;
    } 

עם gethostbyname(), אתה לא יכול להשתמש ב perror() כדי להדפיס הודעות שגיאה (מכיוון ש errno לא בשימוש), במקום, קרא ל herror().

זה די ברור. אתה פשוט מעביר את מחרוזת התויים המכילה את שם המחשב ("whitehouse.gov") ל gethostbyname(), ותופס את המידע מה struct hostent שהוחזר.

המוזרות היחידה האפשרית היא אולי בהדפסת כתובת ה IP, לעיל.h->h_addr הוא מסוג char*, אבל inet_ntoa() רוצה struct in_addr שיעבור אליה, אז אני ממיר (cast) את h->h_addr ל struct in_addr*, ואז משתמש באופרטור * כדי לקבל את המידע .


הקודםביתהבא
מבנים (structs) וניהול נתונים רקע לשרת - לקוח