3.מבנים (structs) וניהול נתונים

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

קודם כל אחד קל: מתאר שקע. מתאר שקע הוא בעל הסוג הבא:


    int

פשוט int רגיל.

עניינים מתחילים להיות מוזרים מפה, אז פשוט תקראו ותיהיו סבלנים איתי. דעו זאת: יש שני סוגים של סידור בתים: הבית הכי חשוב (most significant byte) ראשון, או הבית הכי פחות חשוב (least significant byte) ראשון. השיטה הראשונה נקראת "סדר בתים לרשת". יש מחשבים המאחסנים את המספרים שלהם באופן פנימי בסדר הבית לרשת, ויש מחשבים שלא. שאני אומר שמשהו חייב להיות בסדר הבית לרשת, אתם צריכים לקרוא לפונקציה (כמו htons()) כדי להמיר אותם מסדר הבתים של מארח, לסדר הבית לרשת. אם אני לא אומר "סדר הבית לרשת", אז אתה חייב להשאיר את הערך בסדר הבתים של מארח.

(לאלו הסקרנים, סתר הבתים לרשת , ידוע גם כ "Big-Endian Byte Order".)

מבנה הנתונים הראשוןTM--struct sockaddr. מבנה זה מחזיק מידע על כתובת השקע, לסוגים רבים של שקעים:


    struct sockaddr {
        unsigned short    sa_family;    // משפחת הכתובת, AF_xxx
        char              sa_data[14];  // 14 בתים של כתובת פרוטוקול
    };

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

כדי להתמודד עם המבנה struct sockaddr, מתכנתים בנו מבנה מקביל: struct sockaddr_in (הקיצור in זה לinternet.)


    struct sockaddr_in {
        short int          sin_family;   // משפחת הכתובת
        unsigned short int sin_port;    // מספר פתחה
        struct in_addr     sin_addr;    // כתובת אינטרנט
        unsigned char      sin_zero[8]; // אותו גודל כמו  struct sockaddr
    };

במבנה זה קל לנו יותר לגשת למאפיינים של כתובת שקע. שימו לב ש sin_zero (שנכלל כדי לרפד את המבנה לגודל של struct sockaddr) צריך להיות כולו אפסים באמצעות הפונקציה memset(). בנוסף , וזה החלק החשוב, המצביע ל struct sockaddr_in יכול להיות ממור ע"י casting ל מבציע ל struct sockaddr ולהפך. אז למרות שהקריאה bind() רוצה struct sockaddr* , אתם עדיין יוכלים להשתמש ב struct sockaddr_in , ולהמיר ב casting ברגע האחרון! בנוסף שימו לה ש sin_family תואמת ל sa_family במבנה struct sockaddr, וצריכה להיות "AF_INET" . לבסוף, ה sin_port ו sin_addr חייבים להיות ב סדר הבתים של הרשת!

אבל," אתה מתנגד, "איך מבנה שלם, struct in_addr sin_addr, יכול להיות סדר הבית של הרשת?" שאלה זו דורשת הסתכלות זהירה של המבנה struct in_addr, אחד מהמבנים החיים הנוראים:


    // Internet address (מבנה לסיבות היסטוריות)
    struct in_addr {
        unsigned long s_addr; // זה באורך 32 סיביות, או 4 בתים
    };

זה פעם היה איגוד (union), אבל הימים האלה נראה שנעלמו. ברוך שפטרנו. אז אם אתה הגדרת את ina להיות מסוג struct sockaddr_in, אז ina.sin_addr.s_addr מתיחס ל כתובת ה IP באורך 4 בתים (בסדר הבתים של הרשת). שים לב,אפילו אם המערכת שלך עדיין משתמש באיגוד (union) הנורא, בשביל struct in_addr , אתה עדיין יכול לפנות ל כתובת הIP באורך 4 הבתים בדיוק באותה צורה כמו שרשמתי למעלה (זאת עקב #define-ים.)

המר את המקומיים!

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

יש שני טיפוסים שאתם יכולים להמיר: short (שני בתים) ו long (ארבעה בתים). פונקציות אלה עובדות גם עבור הסוגים הunsigned של הטיפוסים האלה. נאמר תרצה להמיר short מסדר הבית של המארח לסדר הבתים של הרשת. נתחיל עם "h" עבור "host" , אחריו "to", ואז "n" עבור "network", ו "s" עבור "short": h-to-n-s, או htons() (יש לקרוא "Host to Network Short").

זה כמעט קל מידי...

אתה יכול להשתמש בכל צירוף שתרצה של "n", "h","s" ו "l" , לא כולל את אלה הטיפשיים במיוחד. לדוגמא אין פונקציה stolh() ("Short to Long Host") --לא במסיבה הזאת, בכל מקרה. לעומת זאת, כן יש:

עכשיו אתה בטח מתחכם לזה, אתה עלול לחשוב, "מה אני עושה אם אני משנה את סדר הבית ב char?" ואז אתה עלול לחשוב, "אה לא חשוב." אתה גם עלול לחשוב שמכיוון שהמחשב ה 68000 שלך כבר משתמש בסדר הבית של הרשת,אתה לא צריך לקרוא ל htonl() על כתובת ה IP שלך. אתה עלול להיות צודק, אבל אם תנסה להעביר את הקוד שלך למחשב שיש בו סדר בתים הפוך מהרשת, התוכנה שלך לא תעבוד. תיהיה נייד! זה עולם של Unix! (עד כמה של ביל גייטס היה רוצה לחשוב אחרת.) זכור: שים את הבתים שלך בסדר הבית של הרשת, לפני שאתה שם אותם ברשת.

נקודה אחרונה: למה sin_addr ו sin_port צריכים להיות בסדר הבית של הרשת במבנה struct sockaddr_in, אבל sin_family לא ? התשובה: sin_addr ו sin_port מכומסות בשכבות ה IP ו UDP , בהתאמה. לכן, הן חייבות להיות בסדר הבית של הרשת. עומת זאת, השדה sin_family הוא בשימוש רק ע"י הגרעין כדי לדעת איזה סוג של כתובת המבנה מכיל, לכן הוא חייבת להיות בסדר הבתים של המארח. בנוסף sin_family לא נשלח לרשת, לכן הוא יכול להיות בסדר הבתים של המארח.

3.2. כתובות IP ואיך להתמודד עמן

למזלך, יש חבורת פונקציות שמאפשרות לך לתמרן כתובות IP. אין צורך להבינם לבד ולדחוף אותן לתוך long עם האופרטור << .

קודם, נניח שיש לך struct sockaddr_in ina, , ו יש לכם את כתובת IP כזאת "10.12.110.57" שאתה רוצה לאכסן בתוכו. הפונקציה שתרצה להשתמש בה, inet_addr(), ממירה כתובת IP עם מספרים ונקודות לטיפוס unsigned long. ההשמה יכולה להתבצע ככה:


    ina.sin_addr.s_addr = inet_addr("10.12.110.57");

שים לב ש inet_addr() מחזירה את הכתובת כבר בסדר הבית של הרשת -- אתה לא צריך לקרוא ל htonl(). נהדר!

עכשיו, פיסת הקוד למעלה, לא ממש חסונה כי אין בדיקת שגיאות. מבין, inet_addr() מחזירה -1 בשגיאה. זוכר מספרים בינארים? (unsigned)-1 יוצא שמתאים לכתובת IP הזאת 255.255.255.255! זאת כתובת השידור! שגיאה. זכור לעשות את בדיקת השגיאת שלך כמו שצריך.

למעשה, יש ממשק נקי יותר שאתה יכול להשתמש במקום inet_addr(): זה נקרא inet_aton() (כי "aton" אומר "ascii to network"):


    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>

    int inet_aton(const char *cp, struct in_addr *inp);

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


    struct sockaddr_in my_addr;

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

לא כמו שאר הפונקציות הקשורות לשקעים inet_aton(), תחזיר לא-אפס בהצלחה, ואפס בכשלון. והכתובת מועברת בחזרה ל inp.

לרוע המזל, לא כל הפלטפורמות מממשות את inet_aton() אז , למרות שהשימוש בה עדיף, נשתמש בפונקציה ישנה והמוכרת יותר inet_addr() במדריך זה.

יפה, עכשיו אתם יכולים להמיר כתובות IP ליצוג הבינארי שלהן. ומה עם המרה הפוכה? מה אם יש לך struct in_addr ואתה רוצה להדפיס אותו בייצוג של מספרים ונקודות? במקרה זה , אתה תרצה להשתמש ב פונקציה inet_ntoa() (ו "ntoa" אומר "network to ascii") ככה:


    printf("%s", inet_ntoa(ina.sin_addr));

זה ידפיס את כתובת ה IP. שים לב ש inet_ntoa() לוקחת struct in_addr as כארגומנט ולא long. שים לב גם שהיא מחזירה מצביע ל char. זה מצביע למערך char סטטי השמור ב inet_ntoa() ככה שבכל פעם שאתה קורא inet_ntoa() היא תרשום על כתובת ה IP האחרונה שביקשת. לדוגמא:


    char *a1, *a2;
    .
    .
    a1 = inet_ntoa(ina1.sin_addr);  // this is 192.168.4.14
    a2 = inet_ntoa(ina2.sin_addr);  // this is 10.12.110.57
    printf("address 1: %s\n",a1);
    printf("address 2: %s\n",a2);

ידפיס:


    address 1: 10.12.110.57
    address 2: 10.12.110.57

אם אתה צריך לשמור את הכתובות,השתמש ב strcpy() כדי להעתיק אותה למערך char משלך.

זה הכל על הנושא לעכשיו. אחר כך, אתה תלמד להמיר מחרוזת כמו "whitehouse.gov" לכתובת IP התאימה לה (ראה DNS, למטה.)