- הגדרה: פרוטוקול תקשורת ברשת הוא סט של חוקים המגדירים את הפורמט, הסדר והפעולות שיש לבצע בעת שליחה וקבלה של הודעות. כל פעילות התקשורת ברשת נשלטת על ידי פרוטוקולים.
- מודל השכבות (TCP/IP): פרוטוקולי הרשת מאורגנים במחסנית המורכבת משכבות. לכל שכבה תפקיד מוגדר:
- השכבה הפיזית (L1): מטפלת בהעברת הביטים הפיזיים עצמם על גבי מדיה פיזית כמו כבלים או אותות אלחוטיים (חומרה).
- שכבת הקישור (L2): מנהלת את התקשורת הלוקאלית (LAN) בין רכיבים פיזיים סמוכים (לדוגמה: Ethernet).
- שכבת הרשת (L3): אחראית על זיהוי ומיפוי כתובות לוקאליות וגלובליות, ניתוב בין רשתות (IP).
- שכבת התעבורה (L4): אחראית על תקשורת קצה-לקצה בין תהליכים (לדוגמה: TCP, UDP).
- שכבת האפליקציה (L5): מוגדרת על ידי היישום עצמו (לדוגמה: HTTP ו-SMTP המשמשים לגלישה ודוא"ל).
- שכבת האפליקציה ממומשת ברמת קוד המשתמש.
- שכבות התעבורה (L4), הרשת (L3) והקישור (L2) ממומשות לרוב על ידי מערכת ההפעלה. עם זאת, קיימים מקרי קצה בתעשייה שבהם נעדיף להעביר את האחריות למקומות אחרים כדי לשפר ביצועים. למשל, סביבת DPDK מאפשרת לממש שירותי L4 ב-User-level, בעוד ש-RDMA מאפשר מימוש ישיר בחומרת כרטיס הרשת עצמו (NIC).
- השכבה הפיזית (L1) ממומשת על ידי חומרת כרטיס הרשת.
- אנקפסולציה: תהליך בו כל שכבה (החל מהאפליקציה ומטה) מקבלת מידע (Payload) מהשכבה שמעליה, עוטפת אותו עם כותרת (Header) משלה, ומעבירה אותו לשכבה מתחת. כך, הודעת משתמש הופכת ל-Segment (ב-L4), אשר הופך ל-Packet (ב-L3), אשר הופך ל-Frame (ב-L2).
- בצד המקבל מתבצע תהליך הפוך של "קילוף" הכותרות (De-encapsulation) והעברת המידע חזרה מעלה לשכבת האפליקציה.
- אובדן וחוסר סדר ברשת: רשתות הן סביבות שבהן נתונים עשויים ללכת לאיבוד בשל בעיות חשמליות, כבלים שנחתכו, או לחלופין, היגמרות זמנית של מקום בחוצצים (Buffers) בהתקני הרשת - תופעה הידועה גם כבעיית ה-Incast. בנוסף, הרשת עלולה לבלבל את סדר הגעת ההודעות מכיוון שקיימים מסלולים שונים בין המקור ליעד.
- רוב תעבורת האינטרנט מסתמכת על שני פרוטוקולי תעבורה מרכזיים שממומשים על ידי מערכת ההפעלה, המספקים גישות שונות לטיפול באתגרי הרשת:
| מאפיין | TCP (Transmission Control Protocol) | UDP (User Datagram Protocol) |
|---|---|---|
| רמת אמינות | ה-"פרוטוקול שאכפת לו". מבטיח שכל הבתים יגיעו ליעד, ולפי הסדר הנדרש. | ה-"פרוטוקול שלא אכפת לו". שירות Best Effort ללא הבטחת הגעה או סדר. אך מספק הגנה משגיאות (Checksum / CRC) על מנת לבדוק אם נתונים הושחתו בדרך. |
| צורת עבודה | Connection-oriented - דורש משא ומתן להקמת Session מראש בטרם שליחת הנתונים. | Connectionless - כל מנה נשלחת ומטופלת באופן עצמאי ללא משא ומתן מקדים. |
| אבסטרקציית נתונים | מספק אבסטרקציה של זרם בתים רציף (Stream of bytes). | מספק אבסטרקציה של מנות עצמאיות (Datagrams) בגודל מוגדר. |
| בקרות זרימה ועומסים | Congestion Control: שינוי קצב השליחה בהתאם לזיהוי עומסים ברשת. בקרת עומסים ב-TCP מתאפיינת בהתנהגות של "שיני מסור" (Sawtooth), שנוצרת מכיוון שהפרוטוקול מזהה עומס על ידי אובדן של מנות בדרך (Drops) וחותך את קצב השליחה. קיימים אלגוריתמים שונים המנהלים את הקצב הזה, כגון Reno, Cubic, והאלגוריתם BBR שהוצע על ידי גוגל ב-2016. Flow Control: המקבל מפרסם (Advertise) את גודל החוצץ שלו כדי שהשולח לא יציף אותו מהר מדי. |
פשוט יותר, שיהוי (Latency) נמוך, נטול בקרת עומסים, ולכן יכול "להפגיז" נתונים. מתאים לשירותים כמו VoIP. |
- איך TCP מממש אמינות בפועל?
- ACKs (Acknowledgements): הרעיון הבסיסי הוא שהצד המקבל שולח אישור לשולח על כל הודעה שהתקבלה. קבלת ה-ACK מבטיחה לשולח שההודעה הגיעה ליעדה.
- Timeout: אם הודעה הולכת לאיבוד בדרך, השולח לא ימתין ל-ACK לנצח. השולח קובע פסק זמן, Timeout. אם לא התקבל אישור עד תום הזמן, הוא מניח שההודעה אבדה ושולח אותה שוב. לכן חובה על השולח לשמור עותק של ההודעה עד לקבלת האישור.
- מספר סידורי (Sequence Number): מה קורה אם ההודעה הגיעה, אך ה-ACK הוא זה שאבד בדרך? השולח ישלח את ההודעה שוב, והמקבל יקבל הודעה כפולה. כדי למנוע ביצוע פעולות כפולות (כמו העברה בנקאית פעמיים), כל הודעה מקבלת מספר ייחודי עוקב, כך שהמקבל יודע לזהות ולזרוק הודעות כפולות.
- התאמות של TCP לסביבות שונות: קיימות גרסאות של TCP שאינן הדיפולטיביות, אשר מותאמות במיוחד לחוות שרתים (LAN - Data Centers) שבהן ההשהיה (Latency) נמדדת במיקרו-שניות, לעומת רשתות רחבות (WAN) שבהן ההשהיה נמדדת במילי-שניות.
- שמות דומיין (Domain Names): שמות קריאים לבני אדם המיועדים לזהות שרתים (לדוגמה
www.cs.technion.ac.ilאוwww.google.com). שם דומיין אחד יכול להתמפות למספר רב של מכונות שונות, ובכיוון ההפוך (בעזרת שרת DNS המבצע Load Balancing), גם מכונה פיזית אחת (Host) יכולה להחזיק במספר כתובות IP שונות.
- כתובות IP: מחשבים אינם מעבירים מידע באמצעות שמות דומיין, אלא בעזרת כתובת IP.
- IPv4 הוא בגודל 32 ביט. הכתובת מיוצגת לרוב כ-4 אוקטטות (בתים) דצימליות המופרדות בנקודות (Dot-decimal, למשל
132.68.32.1). - IPv6 הוא ההרחבה שנוצרה ל-128 ביט.
- לרוב, כדי שמתכנתים לא יצטרכו לדאוג באיזו גרסת IP המכונה משתמשת (או להקצות זיכרון בגדלים שונים למבנה הנתונים), הם משתמשים בפונקציה
getaddrinfoעם הדגלAF_UNSPEC.
- IPv4 הוא בגודל 32 ביט. הכתובת מיוצגת לרוב כ-4 אוקטטות (בתים) דצימליות המופרדות בנקודות (Dot-decimal, למשל
-
סיכום אבסטרקציות: פרוטוקול
IP (L3)נועד לזהות את ה-Hosts ברשת העולמית, בעוד שפרוטוקוליTCP/UDP (L4)משמשים כדי לזהות ערוצי תקשורת בודדים של התהליכים (Processes) בתוך אותה מכונה.
- Sockets: יישומים (תהליכים) מדברים ביניהם על פני הרשת באמצעות Sockets, המהווים נקודות קצה לתקשורת. קריאה וכתיבה ל-File Descriptor של שקע מיתרגמים בפועל לקבלה ושליחה של נתונים.
- Ports (פורטים): מכיוון שכתובת ה-IP מזהה רק את המכונה ויכולים לרוץ בה מספר תהליכים, נוסף מזהה בשם Port (מספר שלם בגודל 16 ביט). הפורט מאפשר לייחד כל שקע תקשורת.
- סוגי פורטים:
- Well-known (מוכרים): ערכים סטנדרטיים מוגדרים מראש (למשל, תקשורת HTTP מנותבת באופן דיפולטי לפורט 80). פורטים קטנים מ-1024 שמורים לתהליכים פריבילגיים.
- Ephemeral (ארעיים): מוקצים דינמית על ידי מערכת ההפעלה, בדרך כלל עבור צד הלקוח.
- זיהוי חד-ערכי (5-Tuple): Session תקשורתי מזוהה בצורה מוחלטת באמצעות 5 משתנים:
IP מקור,Port מקור,IP יעד,Port יעד, וסוג פרוטוקול תעבורה (UDP/TCP).
-
מודל שרת-לקוח ב-TCP ומעגל חיי ה-Socket:
-
שרת: תהליך פסיבי הפועל ברקע, ממתין לבקשות, ומאזין בכתובת IP ופורט ידועים מראש.
-
רצף קריאות השרת:
socket$\leftarrow$ שיוך הפורט ב-bind$\leftarrow$ האזנה ב-listen$\leftarrow$ לולאה עם קבלת חיבור ב-accept(המייצרת socket ייעודי,clifd)$\leftarrow$ ביצועread/writeמול הלקוח.
-
רצף קריאות השרת:
-
לקוח: תהליך אקטיבי המתחבר לשרת באופן יזום.
-
רצף קריאות הלקוח:
socket$\leftarrow$ התחברות ליעד ב-connect$\leftarrow$ ביצועwrite/readמול השרת.
-
רצף קריאות הלקוח:
-
ייסוד חיבור: תהליך ה-
connectאל ה-acceptכולל לחיצת יד משולשת (3-Way Handshake) כדי להבטיח הקמת חיבור (וגם סגירת חיבור) בצורה מסונכרנת.
-
שרת: תהליך פסיבי הפועל ברקע, ממתין לבקשות, ומאזין בכתובת IP ופורט ידועים מראש.
- פתרון לבעיית ה-Convoy Effect והתמודדות עם ריבוי לקוחות (שרת מקבילי): כאשר שרת קורא וכותב באופן סדרתי, לקוח איטי עלול לעכב את שאר הלקוחות. קיימות שתי גישות מרכזיות לבניית שרתים המטפלים באלפי לקוחות:
- גישה 1 - חוט (Thread) לכל לקוח:
- יתרונות: תכנות פשוט, וניצול יעיל של מעבדים מרובי ליבות.
- חסרונות: צריכת זיכרון רבה (כמה MB לכל חוט), ותקורה כבדה של החלפות הקשר (Context Switches) בין אלפי חוטים.
- גישה 2 - תכנות מונחה אירועים (Event-Driven) באמצעות
select: שרת המבוסס על לולאת אירועים הממתינה ללקוחות ומטפלת בהם באופן רציף.
- גישה 1 - חוט (Thread) לכל לקוח:
פעולה:
המתנה עד שאחד ה-FDs באחת הקבוצות readfds או writefds מוכן לקריאה או לכתיבה (בהתאמה), או עד אשר חלף פרק הזמן המצוין ב-timeout.
- אם קיים FD מוכן לקריאה, אז פעולת
readעליו תחזור מיד ולא תחסום את התהליך. - אם קיים FD מוכן לכתיבה, אז פעולת
writeעליו תחזור מיד ולא תחסום את התהליך.
ערך חזרה: מספר ה-FDs המוכנים בכל הקבוצות. בנוסף, הקבוצות שהועברו כארגומנטים יעודכנו כך שיצביעו על ה-FDs המוכנים.
ניהול fd_set:
כדי לתפעל את סט ה-FDs, משתמשים במאקרוס כפי שמוצג בקוד:
FD_ZERO(&readFDs): לאיפוס ואתחול.FD_SET(fd, &readFDs): להוספת FD ספציפי לסט.FD_ISSET(fd, &readFDs): לבדיקה איזה מה-FDs עורר את הפונקציה לאחר חזרתה.
דוגמת קוד:
int main(void) {
// the server listens to a port
// and then accepts a bunch of sockets (not shown)
while (1) {
// initialize the fd_set to all zero
fd_set readFDs;
FD_ZERO(&readFDs);
// set the bits for the descriptors this server
// is interested in (all from min to max)
for (int fd = minFD; fd < maxFD; fd++)
FD_SET(fd, &readFDs);
// do the select
int rc = select(maxFD, &readFDs, NULL, NULL, NULL);
// check which have data using FD_ISSET
for (int fd = minFD; fd < maxFD; fd++)
if (FD_ISSET(fd, &readFDs))
handle_request(fd); // פונקצית הטיפול בלקוח, נניח כי היא ממומשת בקובץ אחר
}
}חסרונות השימוש ב-select:
- דוגמת הקוד משתמשת בחוט אחד ולכן לא יכולה לנצל מעבדים מרובי ליבות. מימושים חכמים יותר יפזרו את הבקשות בין חוטים שונים של אותו מאגר (thread pool).
- אם הטיפול בבקשה – הווה אומר הפונקציה
handle_request– קורא לפעולה חוסמת (לדוגמה פתיחת קובץ מהדיסק) אז השרת כולו עלול להיחסם. - המימוש של
fd_setמוגבל לקבוצות בגודל מקסימלי של 1024 File Descriptors.
- דקויות בקוד ופונקציות API ברשת:
- הבדלי פונקציות שליחה וקבלה: מעבר ל-
readו-writeהבסיסיות, ב-TCP עובדים לרוב עםsendו-recvהמאפשרות להעביר דגלים מיוחדים. בנוסף, קיימות גרסאות Scatter-Gather הנקראותsendmsgו-recvmsg. מכיוון ש-UDP הוא Connectionless, חובה לציין בכל פעולה מחדש את היעד, ולכן משתמשים ב-sendtoו-recvfrom. בהגדרת השרת מציינים את כתובת ה-IP כ-INADDR_ANYכדי להאזין בכל ממשקי הרשת של המכונה. - מידע על נקודות הקצה: כדי לדעת מהי זהות הלקוח או השרת המחובר ל-Socket מסוים (ה-IP והפורט), נשתמש בפונקציות
getpeernameו-getnameinfo(מומלץ במקוםgethostbynameהמיושנת). - דקויות
getaddrinfo: כדי לתרגם שמות מחשב לכתובות IP בצורת פולימורפית התומכת ב-IPv4 ו-IPv6 ומתמודדת עם Endianness "מאחורי הקלעים". שימו לב להבדל באתחול מבנה ה-hints:- בצד השרת: מעבירים לפרמטר
nodeאת הערךNULL, וב-hints.ai_flagsמעבירים את הערךAI_PASSIVE. - בצד הלקוח: בפרמטר ה-
nodeרושמים את שם השרת, ה-flagsמאותחל ל-0.
- בצד השרת: מעבירים לפרמטר
- Endianness ותרגום כתובות: מכונות שונות שומרות נתונים בסדרי בתים שונים (Big Endian מול Little Endian). כדי ליישר קו לרשת משתמשים בפונקציות המרה כגון
htons.
- הבדלי פונקציות שליחה וקבלה: מעבר ל-
- LAN (Local Area Network): רשת המחברת מחשבים בטווח קרוב (בית, חדר שרתים, משרד) באמצעות חוטים או אלחוטית, בעזרת ציוד מיתוג (Switches), כרטיסי רשת (NICs) ונקודות גישה (APs).
- Ethernet (תקן IEEE 802.3): פרוטוקול התקשורת הדומיננטי ל-LAN, המשלב רכיבי L1 (הגדרת ביטים פיזיים ומדיה כגון סיבים) ו-L2.
- מאפייני תקשורת: העברת המידע נעשית בעזרת יחידות הנקראות מנות (Frames) בשיטה Connectionless ולא אמינה (Lossy).
- מבנה ה-Ethernet Frame: המנה מורכבת מכותרת (Header) הכוללת את כתובת יעד ומקור ה-MAC, ממידע (Payload), ומ"סוגר" (Trailer) המכיל בדיקת יתירות מחזורית (CRC) שנועדה לוודא את אמינות התוכן. אם מנת ה-Ethernet מגיעה ליעד ונכשלת בבדיקת ה-CRC, היא נזרקת (Dropped) באופן מיידי וללא התראה למקור.
- כתובת MAC (Media Access Control): כל רכיב חומרה של Ethernet מזוהה באמצעות כתובת ייחודית בת 48 ביט (6 בתים) הצרובה בו (ROM). כתובת זו משמשת את מתגי ה-LAN כדי לדעת לאן לנתב את המנות בתוך הרשת המקומית.
- האתגר בחיבור רשתות: מתגים ב-LAN "מדברים" רק בשפת ה-L1/L2 (לרוב Ethernet). אך כדי לחבר בין מחשב ברשת אחת למחשב ברשת אחרת (היכולה להיות ביבשת אחרת או מבוססת פרוטוקול L2 שונה לחלוטין), צריך גורם מגשר.
- הראוטר (נתב): רכיב חומרה הנמצא בגבול בין שתי רשתות או יותר, ומסוגל לקרוא ולדבר בשפות ה-L1/L2 של כל אחת מהן. הנתב "מקלף" את כותרות ה-L2 המקוריות של המנה (Packet), מחלץ את מנת ה-IP, ואורז אותה מחדש (Re-encapsulate) לתוך מעטפת L2 המותאמת לרשת הבאה בתור.
-
מקור המונח Internet: האינטרנט איננו רשת אחת, אלא "Inter-network" - רשת עולמית המורכבת מהרבה תת-רשתות נפרדות. תת-הרשתות מיוצגות כצמתים (Vertices) בגרף עצום, בעוד שהראוטרים משמשים כקשתות (Edges) המחברות ביניהן באמצעות שכבת ה-IP האוניברסלית.
- ארכיטקטורת שעון חול (Hourglass): פרוטוקול ה-IP פועל כ"מותניים צרות" - שכבה יחידה שמרכזת הכל. מעליו רצים מגוון פרוטוקולי L4/L5, ומתחתיו קיימות מגוון טכנולוגיות רשת שונות. הודות לכך שכל רשת תומכת IP, האפליקציות חסינות לסוג הרשת שמתחתן.
- פרגמנטציה ופיצול (Fragmentation): פרוטוקול L2 מגדיר גודל מקסימלי לנתונים בודדים הנקרא MTU (ב-Ethernet עד 1500 בתים). מכיוון שמנת IP יכולה לשקול עד 64KB, היא עלולה להיות גדולה מדי לרשת הפיזית. לכן מנות גדולות עוברות פרגמנטציה (פיצול למנות קטנות שיכילו את ה-Headers הנדרשים) במקור, ומורכבות מחדש בנקודת היעד הסופית. כיום, כרטיסי רשת (NICs) יודעים לבצע את הפיצול וההרכבה מחדש בעצמם כדי להוריד עומס מהמעבד באמצעות יכולות הנקראות TSO (TCP Segmentation Offload) בצד השולח, ו-LRO (Large Receive Offload) בצד המקבל.
- כתובת הרשת ו-Subnets: כתובת IP (לדוגמה
132.68.32.1) מתחלקת תמיד לשני תפקידים: הביטים הגבוהים מציינים את הרשת (Subnet part), והביטים הנמוכים מזהים את המחשב הבודד (Host part). חלוקה זו נקבעת על פי ה-Subnet Mask.- לרוב הייצוג נעשה בפורמט CIDR (כמו ב-
/24), המסמן כמה ביטים הוקצו לזהות הרשת מתוך הכתובת. - כל הצמתים בתוך אותו Subnet יכולים להגיע אחד לשני ישירות דרך מתגים ללא התערבות ראוטר.
- לרוב הייצוג נעשה בפורמט CIDR (כמו ב-
- ARP (Address Resolution Protocol - IP ל-MAC): כאשר מחשב רוצה לתקשר עם IP אחר בתוך ה-LAN שלו, הוא חייב לדעת את כתובת ה-MAC הפיזית של היעד. לשם כך, הוא שולח מנת שאילתה ב-Broadcast לכתובת ההפצה
FF:FF:FF:FF:FF:FFשתגיע לכלל המכשירים ברשת. המכשיר לו שייכת ה-IP יזהה אותה ויחזיר את ה-MAC שלו. - NAT (Network Address Translation): ראוטרים ביתיים מספקים חיבור למספר רב של מכשירים בבית למרות שספק האינטרנט (ISP) מקצה להם כתובת IP חוקית פומבית אחת בלבד. תקן ה-IP מקצה טווחי "כתובות פרטיות" (כגון
192.168.0.0או10.0.0.0) שהראוטר מחלק פנימית. הראוטר ממיר ומשחק (Jiggles) עם כתובות ה-IP והפורטים של התקשורת היוצאת והנכנסת כדי לתאם בין העולם החיצוני לכתובת הפנימית האמיתית. - DHCP (Dynamic Host Configuration Protocol): כאשר מכשיר חדש מצטרף לרשת, פרוטוקול אפליקציה זה מחלק לו אוטומטית כתובת IP לשימוש. השרת (לרוב בתוך הראוטר) משיב ומספק בנוסף ל-IP גם את ה-Subnet Mask, ה-Gateway (הראוטר הראשון ליציאה החוצה), ואת שם וכתובת שרת ה-DNS.
- האנקפסולציה ב-DHCP: התקשורת מתבצעת כבקשת Broadcast. הבקשה המקורית של האפליקציה (L5) עטופה ב-UDP (לכדי L4 Segment), שעטופה ב-IP (לכדי L3 Packet), שעטופה ב-Ethernet Frame (רמת L2) עם כתובת יעד Broadcast של ה-MAC (כלומר
FF:FF:FF:FF:FF:FF) כדי שתגיע לכל הציוד ב-LAN המקומי.
- האנקפסולציה ב-DHCP: התקשורת מתבצעת כבקשת Broadcast. הבקשה המקורית של האפליקציה (L5) עטופה ב-UDP (לכדי L4 Segment), שעטופה ב-IP (לכדי L3 Packet), שעטופה ב-Ethernet Frame (רמת L2) עם כתובת יעד Broadcast של ה-MAC (כלומר