שכבת התעבורה

שכבת התעבורה אחראית להעביר מידע מProcess ל Process מרוחק. כחלק מכך, יש לה שתי מטרות עיקריות:

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

ב) העברה אמינה של מידע (אופציונלי).

אנחנו כבר יודעים איך שכבת התעבורה מאפשרת עבודה מול כמה שירותים של אותה מערכת Ports.
Port הוא מספר בטווח [0,65535] . על מנת שתוכנה אחת תוכל להתחבר לתוכנה מרוחקת, עליה לדעת על איזה Port התוכנית מאזינה. לכן ישנם מספר פורטים מוכרים. אלו הם הפורטים [0,1023] וכמוסכמה הם אמורים להיות תפוסים , למשל פורט 80 שייך לפרוטוקול http .

פרוטוקולים מבוססי קישור ולא מבוססי קישור

בשכבת התעבורה פרוטוקולים יכולים להיות Connection Oriented או Connection Less

Connection Oriented

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

Connection Less

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

למה צריך Connection Less?

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

UDP VS TCP

הפרוטוקולים הכי נפוצים בשכבת התעבורה הם UDP ו TCP וכל מהם הוא סוג אחר של חיבור עם יתרונות וחסרונות משלו. לפני שנדבר על כל אחד בנפרד נשווה בינהם עם הפרמטרים החשובים:

Screenshot 2024-01-20 at 0.55.34.png

UDP

פרוטוקול שאינו מבוסס קישור. לכן הוא אינו מבטיח הגעה של המידע כלל והגעה בסדר הנכון בפרט.
ב header של פקטת UDP יש בסך הכל את השדות הבאים:

א. Source Port
ב. Destination Port
ג. Length - גודל ה header והמידע יחד בפקטה

Pasted image 20240119123907.png|450
Pasted image 20240119123313.png|450
ניתן לראות בעצם שגודל הheader הוא 8 בתים וגודל כל הפקטה הוא 32 בתים השדה של Length מציין את הגודל 40. הסיבה שהשתמשתי כאן ב DNS הוא כי זה פרוטוקול של שכבת האפליקציה שממומש באמצעות UDP.

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

Checksum

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

השולח משתמש באלגוריתם כדי לחשב את הChecksum של המידע. כאשר המקבל תופס את המידע, הוא מחשב את הchecksum בעצמו באותו האלגוריתם ומשווה עם הchecksum שעבר. אם יש התאמה אז אין שגיאות

Screenshot 2024-01-19 at 13.06.57.png|350

במקרה של UDP המידע מחולק לחתיכות של 16 ביט. החתיכות האלה מחוברות יחדיו, אם יש carry מוסיפים אותו ל sum. לאחר מכן מפעילים one's complement על הסכום (not על הביטים) וזה ה checksum.

למשל נסתכל על הdata הבא:

011001100110000001010101010101011000111100001100

נסכום את השניים הראשונים נקבל :

0110011001100000+01010101010101011011101110110101

לתוצאה נוסיף את 16 הביטים הנותרים :

1011101110110101+10001111000011000100101011000001

נוצר לנו carry ולכן יש להוסיף אותו לסכום (wraparound) ונקבל

0100101011000001+1=0100101011000010

לבסוף נבצע not על התוצאה ונקבל: checksum=1011010100111101

בצד המקבל נעשה את אותו חישוב של הסכימה, לבסוף נסכום עם הchecksum שהעברנו ואם הסכום יוצא שכל הביטים הם 1 סימן שהמידע אמין. אם אחד הביטים הוא 0 סימן שישנו שיבוש במידע.

Warning

ברגע שהשגיאה היא יותר מbit אחד יש מצב שהchecksum יצא אותו דבר למרות שהייתה שגיאה. כמו כן יכול להיות שגיאה בchecksum עצמו..

TCP

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

TCP תוכנן ועוצב לרוץ מעל שכבת רשת שאינה אמינה. כלומר, הנחת הבסיס היא שבשכבת הרשת חבילות יכולות ללכת לאיבוד או להגיע בסדר לא נכון.

Screenshot 2024-01-19 at 13.46.27.png|350

כיצד ניתן לוודא שהמידע מגיע אל היעד? וכיצד ניתן לוודא שהוא מגיע בסדר הנכון?

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

ראשית, אנו יכולים לתת מספר סידורים לחבילות שלנו. נאמר שבשכבת האפליקציה רצינו לשלוח את המידע "Hello Cool Network" והחלוקה לחבילות הוא חבילה פר מילה.

נוכל לשלוח את החבילות כשלצידן יש מספר סידורי

Screenshot 2024-01-19 at 14.16.33.png

בצד שרת חבילות יכולות ליפות ונניח שחבילה 2 נפלה בדרך. בשיטה שלנו השרת יבין שחסרה לו חתיכה מספר 2.

Screenshot 2024-01-19 at 14.17.23.png

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

Screenshot 2024-01-19 at 14.18.59.png

בצד לקוח, אם לא התקבלה חבילת ACK מהשרת לאחר זמן מסוים, כנראה שהחבילה שהוא שלח ״נפלה״ ובמקרה כזה היא תשלח שוב

Screenshot 2024-01-19 at 14.19.42.png

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

Screenshot 2024-01-19 at 14.20.29.png

המימוש ב TCP

ה headers ב TCP מכיל את אותם השדות של UDP ושדות נוספים, ההבדל הוא שבמקום length יש head-length שמייצג רק את הגודל של הheader.

באמצעות הchecksum אפשר לגלות שגיאות, אך אם נגלה שגיאה ב UDP הוא פשוט יזניח את הפקטה, כאן צריך להשתמש במנגנון ה ACK כדי לאשר שאכן המידע הגיע תקין או NAK כדי להודיע שהמידע הגיע לא תקין. בגלל שגם ACK/NAK יכול להשתבש בגלל שגיאות אנחנו נכניס אותו כ header TCP מלא בתשובה ונניח שהchecksum יכול לתפוס את זה. (זה במקום לשלוח פשוט ACK bit).

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

Screenshot 2024-01-19 at 16.04.31.png

המחרוזת Hello תוקבל למספרים הסידוריים 100,101,102,103,104,105 כאשר התו האחרון הוא הרווח שכן המחרוזת הנ״ל היא חלק ממשפט שלם.

החבילה השנייה התחילה עם המספר הסידורי 106. המשמעות של כך היא שהבית הראשון שבה כלומר התו c הוא בעל המספר הסידורי 106. כלומר המשך התקשורת ייראה כך :

Screenshot 2024-01-19 at 16.07.09.png
נשים לב שמדובר בתקשורת בין הלקוח לשרת בדוגמה, אך באותו אופן זה יכול להיות בין שרת ללקוח.

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

ה client שולח את ההודעה הראשונה לשרת ומחכה לack . רק אחרי שהוא מקבל את הack הוא יעביר את ההודעה הבאה.

Screenshot 2024-01-19 at 15.10.24.png

המנגנון הזה נקרא Stop & Wait כלומר אם התקבל nak נשלח שוב את הפקטה שעבורה התקבל nak.
נשים לב שהשרת שולח כresponse את ה tcp header בלי data.

Screenshot 2024-01-19 at 15.52.22.png

מכיוון שהמספרים הסידוריים של TCP מתייחסים לבתים ברצף המידע כך גם מספרי ה ACK. מספר ה ACK ב TCP מציין את המספר הסידורי של הבית הבא שמצופה לקבל. כך למשל עבור הדוגמה הקודמת

Screenshot 2024-01-19 at 16.04.31.png
נקבל
Screenshot 2024-01-19 at 16.18.49.png

בצורה זו קל מאוד לבצע מעקב אחרי התקשורת. מכיוון שה ACK מכיל את המספר הסידורי הבא, הרי שזה יהיה המספר הסידורי שיישלח בחבילת המידע הבאה. כך בדוגמה זו, רצף החבילות יראה כך:

Screenshot 2024-01-19 at 16.19.33.png

בנוסף, כאשר נשלח ACK ב TCP, הכוונה היא שכל המידע שהגיע עד לבית שמצוין ב ACK הגיע באופן תקין. כך לדוגמה, במקרה לעיל השרת יכול היה לא לשלוח ACK עבור החבילה שכללה את המידע Hello אלא רק לאחר קבלת החבילה שכללה את המידע cool. במקרה זה, ערך ה ACK צריך להיות המספר הסידורי הבא - 110.

Screenshot 2024-01-19 at 16.23.32.png

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

TCP Segmentation

כפי שהסברנו, TCP מחלק את המידע לפי bytes. בפועל הוא מבצע סגמנצייה שגודל של כל סגמנט הוא MSS. זה הגודל המקסימלי של מידע עבור שכבת האפליקציה שיכול להיות בתוך פקטה, כך ש כאשר מוסיפים את כל הheaders הדרושים האורך הכולל הוא לכל היותר MTU שזה אומר -maximum transmission unit, בערך 1500 בתים.

Screenshot 2024-01-19 at 16.31.42.png
בדוגמה אפשר לראות ששלחנו בשכבת האפליקציה 7000 בתים של מידע. כמובן שלשכבת האפליקציה לא אכפת מגודל המידע. בשכבת התעבורה tcp מחלק את המידע ל7 חתיכות של 1000 בתים. נשים לב ש UDP היה מאציל את הדרישה הזאת לחלוקה עבור שכבת הרשת. אם כן, TCP מחזיר באפר ששומר את המידע ומחלק אותו לsegments שאת כל אלה עוטף ב headers ומעביר לשכבת הרשת.

Screenshot 2024-01-19 at 16.34.41.png

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

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

לשם כך, משתמשים בheaders שהסברנו עליהם למעלה. seq num, ack num.

Screenshot 2024-01-19 at 16.38.52.png|350

נתבונן על דוגמה מפורטת יותר מההסבר הנ״ל על echo tcp server ונסביר אותה

Screenshot 2024-01-19 at 16.48.59.png
ראשית נשים לב שבפועל כל אפליקצייה שמתקשרת באמצעות TCP מחזירה Send buffer ו Receive buffer. הראשון הוא המידע שנשלח ואיזה מידע קיבל כבר ACK. השני הוא עבור מידע שהתקבל ואיזה מהם דווח עליו ACK.

כאשר A שולח את התו C הוא מדווח גם את המספרים seq=42 וגם ack=79. הוא בעצם אומר ל B שהוא עכשיו מדווח על הבית עם המספר הסידורי 42 וגם הוא מדווח לB שהוא כבר דיווח ack עד הבית ה79.

ברגע ש B מקבל את התו, הוא שומר אותו receive buffer וכעת מדווח ack על הביט ה 43 כמו כן הוא שולח לו בחזרה את התו כי זה שרת אקו ומוסיף אותו ב send buffer אבל המספר הסידורי של C ב באפר הזה הוא 79 ולכן seq = 79, כלומר הוא מדווח שהוא שולח את הבית עם המספר הסידורי 79.

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

Info

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

Packet timeout

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

לשם כך tcp רוצה לקבוע זמן שגבוה מ RTT(Round Trip Time) אבל זה זמן שמשתנה מפקטה לפקטה.

Screenshot 2024-01-19 at 17.15.10.png|450
נסתכל על A,B כצירי זמן של כל אחד מהמכשירים.

RTT מורכב מכמה חלקים:
א. השהיית שידור, כלומר הזמן שלוקח לשדר את הביט הראשון עד הביט האחרון בשליחת segment. יש אותו גם ב B וגם ב A בגלל שגם B משדר ACK בחזרה. זאת פונקציה של כמה ביטים יש לשדר ביחס לקצב השידור.

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

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

ג. השהיית השידור של הACK (בשרטוט זה בנקודה קטנה בגלל שזה זמן יחסית קטן ביחד לביטי המידע)

ד. השהיית ההתפשטות של הביט האחרון של ה ACK

סה״כ:

RTT=dt+dp+dtack+dpack

כאשר מתקיים ברוב המקרים ש dtack0 .

Store and forward:
ההסבר הנ״ל התעסק בשני מכשירים באותה הרשת. כאשר הם ברשת אחרת המצב שונה כיוון שהמידע צריך לעבור דרך הראוטר. זאת שיטה אחת שבה עובדים רכיבי תקשורות והיא פשוטה.
עד שלא מגיע הביט האחרון של החבילה, הוא לא מעביר אותה הלאה , כלומר נוצר פה רובד נוסף שמוסיף ל RDT , אם כי הנוסחה לא משתנה. באופן די אינטואיטיבי המשמעות היא שככל שיש יותר רכיבי תקשורת בדרך ליעד ככה יעלה זמן הRTT.

במילים אחרות ככל שמוסיפים לנו ראוטרים מטווחים ככה הנוסחה לחישוב RTT גדלה. אבל הרעיון הוא אותו רעיון בגלל שבstore and forward מחכים תמיד לביט האחרון של חבילה כדי לשלוח אותה הלאה.

שערוך RTT

יש צורך לבצע שערוך של הRTT כדי לדעת לאמוד את זמן ה timeout. שכן על כל פקטה צריך לבצע שערוך ל RTT הנ״ל לפני שהמידע על הRTT התקבל בפועל. נסמן SampleRTT כ RTT האחרון שנדגמה. נרצה לבצע ממוצע של מספר דגימות כאלה ולבצע שערוך.

נגדיר :

EstimatedRTT=(1α)EstimatedRTT+αSampleRTT

בעצם מדובר פה במעין ממוצע משוקלל, שככל שα יותר נמוך ככה נותנים יותר משקל להערכות הקודמות. בדרך כלל α=0.125 . כמובן ש(1α)EstimatedRTT מייצג את ההערכה הקודמת כפול המשקל שנותנים לה.

Screenshot 2024-01-19 at 18.41.47.pngֿ

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

בגלל שהסטיות האלה גדולות מאוד נרצה לצמצם את ההפרשים האלה באמצעות חישוב הסטייה/שונות של הממוצע שלנו. הנוסחה תייצג את הסטייה בין המדידה להערכה בכל פעם.

DevRTT=(1β)DevRTT+β|SampleRTTEstimatedRTT|

ההפרש מייצג את ההפרש הנוכחי והסטייה בנוסחה היא הסטייה הקודמת שנמדדה. ככל שהרשת יציבה יותר ככה הסטייה נמוכה יותר ואם הרשת אינה יציבה אז נשתמש בסטייה כדי לתקן את הtimeout כפי שתיכף נראה. בד״כ β=0.25 .

סך הכל נגדיר ש

Timout=EstimatedRTT+4DevRTT

Stop and Wait Efficiency

בשיטת Stop and Wait יש מצב של חוסר יעילות בגלל under utilization, אין שום סיבה שנחכה לקבל ack על חבילה כדי לשלוח את הבאה בתור. נרצה שכל צד בתקשורת יחזיר באפר מידע שקובע כמה הוא קיבל וכמה הוא שלח ובאמצעות השיטה הזאת נוכל להשתמש במנגנון pipeline, שמאפשר לנו לשלוח מספר חבילות במקביל.

Pipeline

Screenshot 2024-01-19 at 18.59.17.png

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

המימוש:
נגדיר sliding window להיות מספר החבילות שאפשר לשלוח לפני שמקבלים ACK על אחת מהן. במילים אחרות, כמה חבילות ניתן לשלוח ״במכה״ אחת.
Pasted image 20240410121542.png
בתמונה ניתן לראות receiver שגודל החלון שלו הוא 3 ולכן ה sender שולח רק 3 חבילות במכה אחת. ברגע שהחבילה הראשונה בשלישייה שלחה ack הsender יכול לשלוח את החבילה הבאה בתור (הרביעית).

Warning

הפתרון הזה מייצר לנו בעיה חדשה, כעת אם חבילה נופלת ולא מגיעה, יש צורך גם לעדכן איזה חבילה נפלה בדרך (בניגוד ל stop and wait שתמיד ידענו איזה חבילה הלכה לאיבוד בגלל שתמיד יש אחת בתנועה ויש עליה timeout).

ננסה לפתור את הבעיה אך ראשית נבין קצת יותר טוב את מנגנון ה Slide Window:
Screenshot 2024-01-20 at 0.00.52.png
נניח ונרצה לשלוח 8 segments של מידע והחלון הוא בגודל 3.

על כל פקטה בבאפר (בין אם זה ה send buffer ובין אם זה ה receive buffer) נשמור את אחד מהstates הבאים
א. האם היא ACKed
ב. האם היא נשלחה ועדיין לא קיבלה ACK.
ג. האם היא ניתנת לשליחה
ד. לא ניתנת לשליחה כרגע.

הפרמטרים הללו נקבעים לפי ה window size והבאפרים עליהם דיברנו מקודם.

כאשר מתקבל ack על החבילה הראשונה הsliding window יזוז ימינה.

Screenshot 2024-01-20 at 0.03.48.png
נשים לב שאם מקבל ack על חבילה 3 אבל על חבילה 2 טרם הגיע ack עדיין לא נוכל להזיז את החלון.

GBN

אחת השיטות לטיפול בבעיה, היא שיטת Go Back N. לפי השיטה:

  1. לשולח יכול להיות עד n חבילות שלא קיבלנו עליהן ack.
  2. מקבל שולח ack מצטבר בלבד. כלומר לא עושה ack לחבילה כאשר יש חור בחלון.
  3. לשולח יש טיימר עבור החבילה הישנה ביותר שלא עשו עליה ack. אם וכאשר הטיימר מסתיים, הוא שולח מחדש את כל החבילות שלא עשו להן ack עדיין.

נסתכל על הדוגמה הבאה:

Screenshot 2024-01-20 at 0.27.22.png

  1. החלון בגודל N=4
  2. חבילה 0 מגיעה כמו שצריך ומתקבל עליה ack. באופן דומה גם חבילה 1. מה שגורם לsliding windows לזוז ולשלוח את חבילות 4 ו 5.
  3. חבילה 2 הלכה לאיבוד. כל חבילה שיתקבל עליה ack כך שהמספר הסידורי שלה גדול מ2 תזרק עד שיתקבל timeout על 2.
  4. ברגע שהתקבל timeout על 2 כל החבילות מ2 עד end of window נשלחות מחדש.
  5. נשים לב שלאורך הדרך הsliding window לא זז בגלל שהיה את ה״חור״. את החור ניתן לזהות באמצעות ה sequence number.

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

Selective Repeat

שיטה נוספת לטיפול בבעיה שתיארנו.
כאשר הפקטה הi נאבד ומגיעה הפקטה הi+1 אנחנו לא מזניחים אותו. אלא שומרים אותו בArray ונשלח עליו ACK. באותו אופן על הבאים בתור אחרי i+1, נשמור אותם במערך ונדווח עליהם ACK.
ברגע שהפקטה הi קיבלה timeout נשלח אותה שוב, וכשנקבל עליה ACK החלון יזוז לפי גודל המערך כלומר לפי כמות הפקטות ששמרנו בצד עד שהתקבל הtimeout. כמובן שברגע שACK מגיע לפקטה הi מרוקנים את המערך.

נסתכל על דוגמה עם חלון בגודל 4.
Screenshot 2024-01-20 at 0.34.58.png

  1. הack של חבילות 0,1 חוזרות ולכן שולחים את חיבלות 4,5.
  2. חבילה 2 הלכה לאיבוד ולכן כשחבילה 3 תקבל ack לא נזניח אותה כמו מקודם אלא נשמור את ack3 במערך.
  3. נשים לב שכאשר חבילה 3 מגיעה לא ממשיכים לשלוח חבילות כי חבילה 2 תוקעת אותנו.
  4. כאשר timeout על חבילה 2 יתקבל, נשלח רק אותה שוב ונמשיך לצבור את הack על החבילות 4 ו 5.
  5. מה יקרה כשחבילה 2 תקבל ack? נוכל להזיז את הwindow כגודל המערך שצברנו כלומר, עבור 2,3,4,5 סך הכל נזיז את החלון 4 איברים, תחילת החלון תהיה החבילה ה6.
    Screenshot 2024-04-10 at 13.40.18.png
Warning

חשוב לשמור על יחס של קטן ממש בין ה window size ל seq numbers שכן, כיוון שה seq הוא מספר סופי וחסום בגודל של 32 בתים, אם הdata שאנחנו נרצה לשלוח גדול מזה, יהיה wrap around של ה seq number. למשל, אם seq=[0,3] והdata הוא בגודל 6 בתים, אז הסדר של המזהים של הבתים יהיה מהצורה 0,1,2,3,0,1,2 . במצב זה, אם הwindow גדול מדי יכול להווצר מצב שבו המידע התקבל בצד המקבל אבל ה ACK לא הגיע לשולח ובמצב זה הוא ישלח בית שהתקבל עם אותו seq number כמו הבית החדש שהצד המקבל ממתין לו. הפתרון של זה הוא לשמור על size(window)max(seqNumbers)/2
Screenshot 2024-01-20 at 0.52.07.png|350

סיכום עד כאן:

  1. TCP הוא פרוטוקול point to point. כלומר, בניגוד ל UDP ששם המקבל יכול לקבל חבילות מהמון שולחים על אותו socket ולהגיב לכל חבילה בנפרד, ב TCP יש לנו על סוקט בודד גם שולח אחד וגם מקבל אחד.
  2. TCP נותן תקשורת אמינה ולפי הסדר, המידע נשלח כ stream ולא הודעות. כל דחיפה של מידע נשמרת בבאפר והסוקט מסתכל על הבאפר ובוחר כיצד לחלק את החבילות (segmentation).
  3. TCP משתמש ב pipeline כדי לשפר את היעילות אבל גם משתמש במנגנון בקרת עומסים שתיכף נדבר עליו כדי לוודא שלא שולחים יותר מדי או בקצב גבוה מדי למצב שהרשת או היעד לא יכולים לעמוד בעומס.
  4. תקשורת ב TCP הוא full duplex כלומר דו כיוונית.
  5. התקשורת היא מוכוונת חיבור כלומר יש צורך ב handshake.

TCP Headers

Pasted image 20240120010030.png

  1. U,A,P,R,S,F - דגלים בגודל ביט אחד
    1. U- מלשון urgent data.
    2. A - מלשון ACK, דגל שקובע האם יש להתייחס למספר ה ack.
    3. P- מלשון Push דגל שמאפשר למידע להקלט בלי לחכות בבאפר.
    4. R - מלשון reset. מודיע שצריך לעצור את החיבור ולהתחילו מחדש.
    5. S- מלשון synchronized. נפרט עליו בהמשך.
    6. F- מלשון FIN , נשלח כאשר מעוניינים לסגור את החיבור.
      recieve window- כפי שניתן לראות הוא חלק מהתחיליות. נפרט בהמשך למה.

TCP 3-way Handshake

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

באופן כללי, הקמת קישור ב TCP נקראת Three Way Handshake ונראת כך :
Screenshot 2024-01-20 at 1.10.55.png
כפי שניתן לראות, במהלך הרמת הקישור נשלחות שלוש חבילות, בכל אחת מהן גם יש שימוש ב ack ו seq.

SYN

בשלב הראשון, הלקוח שולח לשרת חבילה שמטרתה להתחיל את הקמת הקישור. על ידי הדלקת הheader : SYN הלקוח מעיד על כוונתו לפתוח קישור. ה seq המצורף נקרא ISN- initial sequence number והוא נבחר באופן רנדומלי כדי למנוע התנגשויות של חיבורים, אם כל החיבורים היו מתחילים אם מזהה 0 למשל, אז אם חיבור כלשהו ייפול תוך כדי שפקטה עם מספר סידורי כלשהו תהיה בדרך היא עלולה להגיע לחיבור אחר שיוקם תוך כדי וגם הוא יהיה עם המזהה 0. כדי למנוע מקרים כאלו מתקבל מספר רנדומלי כISN.

כמו כן נשים לב ש ACK כבוי כי לא ניתן ACK על אף חבילה קודמת (זאת הראשונה).

SYN+ACK

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

הseq של החבילה של השרת יהיה ISN של התקשורת בינו לבין הלקוח וגם הוא ייבחר באופן אקראי. זאת כמובן שתקשורת היא דו כיוונית ונעשת בשני Streams של מידע , רצף בתים ללקוח ורצף בתים לשרת.

לסיום על השרת לציין את מספר ה ACK כדי להודיע ללקוח שהוא קיבל את החבילה שלו, כפי שהוא עושה גם במצב רגיל. כלומר הערך של ה ACK יהיה לפי הבית הבא שהשרת מצפה לקבל מהלקוח שבמקרה הזה הוא 1 (כי זה מה שהתקבל מהדגל SYN) בתוספת הערך הראשוני של seq נסמנו x ולכן ACK=x+1 .
Screenshot 2024-01-20 at 1.24.13.png

ACK

על מנת שהחיבור יוקם בהצלחה הלקוח צריך לדווח לשרת שהחבילה SYN+ACK התקבלה בהצלחה וששני הצדדים מסונכרנים על המספרים הסידוריים הראשוניים אחד של השני.

כדי לבצע זאת, הלקוח שולח חבילה כשדגל ה ACK דולק, ומספר ה ACK מציין את הבית הבא שהוא מצפה לקבל מהשרת. כלומר המספר הסידורי + 1 (בידיוק כמו מקודם). הפעם הדגל SYN כבוי, שכן זו כבר לא החבילה הראשונה שנשלחת מהלקוח לשרת בקישור הנוכחי. כמו כן הseq של החבילה הנשלחת יהיה הערך שהיה ב ack בחבילה שהתקבלה מהשרת..

Screenshot 2024-01-20 at 1.28.44.png

סך הכל אם נתאר את כל התהליך מהתחלה עד לסיום זה יראה כמו בדיאגרמה הבאה:

Pasted image 20240120012925.png

TCP Connection Termination

Screenshot 2024-02-08 at 11.58.37.png
כאשר נרצה לסגור את החיבור באמצעות socket.close הדגל FIN ישלח דלוק עם seq number רלוונטי לפי הפעולות שהתבצעו קודם לכן.

הצד שמאשר את הסגירה של החיבור שולח כמובן ACK ו ACKnum .
צד זה עדיין יכול להמשיך לשלוח מידע וברגע שהוא יסיים הוא ישלח בעצמו את הדגל FIN דלוק.

במידה ושני הצדדים מוכנים לסגור את התקשורת ניתן לסגור את החיבור בשלושה שלבים:
הלקוח שולח FIN , השרת מחזיר FIN+ACK והלקוח מחזיר ACK על ה FIN.

TCP Retransmission

ישנם כל מיני מקרי קצה ש TCP צריך להתמודד איתם כדי שיתאפשר לשלוח את המידע לפי הסדר. כך למשל יכול להיות מצב שהמידע ממחשב א׳ יגיע למחשב ב׳ אבל ה ACK עבור המידע זה יפול בדרך ונקבל Timeout. במצב זה אנחנו יודעים שמחשב א׳ שולח שוב את המידע בציפייה לקבל את הACK למרות שמחשב ב׳ כבר קיבל אותו ודיווח (כאן נכנס לתמונה ה SEQ שמאפשר לצד המקבל לדעת שהוא לא קיבל מידע חדש).

Pasted image 20240208211730.png|450

נשים לב שאם נפל ה ACK ולפני שהתקבל TIMEOUT על החבילה כבר נשלח ACK על החבילה הבאה (כי עובדים בשיטת Pipeline) אז בגלל שה ACK הוא מצטבר המחשב השולח ידע שהוא לא צריך לשלוח את החבילה שוב ויכול לשלוח את החבילה הבאה.

Pasted image 20240208212814.png|450

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

Pasted image 20240208212450.png|450
נשים לב שהצד השולח שומר את ה ACK שהוא קיבל עד לרגע הנוכחי במשתנה SendBase.

ACK Events

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

למשל:
א) אם המידע מגיע כמו שצריך לצד השולח TCP משתמש במנגנון של delay על ACK. כלומר הוא מחכה ורק אם במהלך הדיליי לא התקבל שום חבילה חדשה הוא שולח את אותו ACK.

ב) במידה והגיעה חבילה חדשה בזמן הזה הוא מגיע לאירוע 2 בטבלה למטה ושולח את ה ACK המצטבר עבור שניהם.

ג) אם יש מצב שבו מגיע מידע עם seq שלא תואם את המספר seq שהצד המקבל מצפה לו הוא ישלח ACK נוסף (לא מצטבר) עבור הפקטה הרלוונטית האחרונה שנשלחה לפי הסדר.

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

Pasted image 20240208215156.png

TCP Fast Retransmit

זה מנגנון שנועד להעריך שחבילה הלכה לאיבוד לפני שהתקבל עליה Timeout. המנגנון משתמש בטכניקת ה duplicate ACK שציינו מקודם. במצב שבו מגיעות יותר מ3 חבילות שהן out of order בגלל חבילה ספציפית אז TCP מיד ישלח אותה שוב ולא יחכה ל timeout. כפי שאמרנו אנחנו שולחים ack כפול ומצפים לקבל את החבילה אבל חבילות אחרות מתקבלות והיא עדיין לא מגיעה.

Pasted image 20240208220839.png|450

TCP Flow Control

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

החלון הראשון הוא חלון בקרת הזרימה:
Pasted image 20240208222855.png
חלון זה שומר את ה data ב buffer ומחכה ששכבת האפליקצייה תשלוף אותו. כאשר אין מקום בבאפר הצד המקבל צריך לתאם את זה עם הצד השולח כדי שלא יגרום ל overflow ולנפילת חבילות בעקבות כך.

TCP עושה את זה באמצעות הheader שנקרא received window .

Pasted image 20240208224006.png
TCP ישלח את rwnd שזה הגודל הפנוי בבאפר לצד השולח וכך הוא ידע האם אפשר להמשיך להזרים עוד מידע או לא.

Pasted image 20240208224636.png

הניהול של זה ברמת TCP הוא יחסית פשוט.
כפי שאמרנו אם יש מידע בגודל מסויים TCP מחלק אותו לפקטות לפי ה MSS ומתחיל תהליך של three way handshake.
במהלך התהליך הזה הצד השולח יקבל את הגודל של הrwnd וישמור אותו בפרמטר שנקרא send window. נחזיק שני מצביעים LBA ו LBS שזה ה last byte ack ו last byte sent שכן אמרנו שה seq וה ack מתקבלים ונשלחים ברמת הבתים.
קל להבין למה הנוסחה data we can send = send window - (LBS-LBA) מתאימה לנו שכן אנחנו יכולים לשלוח חבילות בהתאם למגבלת החלון וגם בהתאם לכמה בתים לא קיבלנו עליהם ack. אם ההפרש הוא 0 זה אומר שההפרש בין המצביעים הוא בדיוק כגודל הפנוי של הבאפר.

נסתכל על דוגמה - נניח שאנחנו רוצים לשלוח 15KB מאליב לבוב ונתון MSS=1500B וכי באפר הקבלה של בוב הוא בגודל 4500.

הצד שולח מעוניין להעביר 10 segments של מידע אבל מכיוון ש receive window יהיה 4500B אז הגודל של הwindow יהיה 3.
Screenshot 2024-04-11 at 14.39.01.png

החבילות 1,2,3 הן החבילות שנשלח בשיטת pipeline לבוב, יהיה לנו מצביע LBA ל 0 ו LBS ל 3 (נשים לב שהמספר 3 הוא ברמת הבתים כלומר הערך בפועל יהיה 4500). ברגע ש 1 יקבל ACK וגם שכבת האפליקציה של בוב קראה את 1 מהבאפר נעשה מספר דברים:

  1. נזיז את LBA אבל לא את LBS
  2. נתאים את גודל המערך לפי ה send window החדש שהתקבל עם ה ack על הסגמנט הראשון.

Screenshot 2024-04-11 at 14.40.46.png

כלומר מספר הבתים החדשים שנוכל לשלוח הוא 4500(45001500)=1500.

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

Screenshot 2024-04-11 at 14.45.47.png

Congestion Control

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

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

כדי להבין את TCP מטפל בזה צריך להגדיר מספר מצבים

slow start

Pasted image 20240208230948.png|450

כאשר החיבור מתחיל, נגדיר באופן מעריכי את קצב שליחת הפקטות עד שנקבל מאורע של איבוד מידע.
מתחילים מזה ש ה cwnd=1MSS ומגדילים את cwnd ב 1 כל ACK שמתקבל.

AIMD

Additive Increase Multiplicative Decrease הוא מנגנון למניעת עומס כאשר עובדים בשיטה הקודמת.

השיטה הזאת עובדת בהגדלה ליניארית של ב cwnd. כלומר במקום להגדיל ב 1 את החלון כל ACK שמתקבל מה שיגרום לכך שבכל RTT נגדיל את החלון בגודל אקספוננציאלי ביחס להגדלה הקודמת. הפעם מגדילים ב 1 לכל RTT בלבד. למשל אם ה RTT הוא 1ms אז כל פרק זמן זה נגדיל את החלון ב1 , זה גורם לכל שב RTT הi נשלח i פקטות במקום 2i פקטות.

את המנגנון הזה עושים עד שמזהים איבוד מידע. ברגע שמזהים אבדה מקטינים בחצי את ה cwnd.

Pasted image 20240208235617.png

המעבר בין slow start ל AIMD נעשה באמצעות פרמטר שנקרא:
SST- Slow Start Threshold. כאשר כמות הפקטות שאפשר לשלוח ברגע נתון מגיעה לערך של הSST נעבור ל AIMD.

בכל פעם שמתקבל lost יש שני דרכים לטפל:
א) מקטינים את cwnd להיות 1 ומקטינים את ה SST להיות חצי מcwnd לפני ההקטנה. עובדים בשיטת slow start עד שמגיעים שוב ל ssthresh. ככה זה עובד ב TCP Tahoe (גרסה מסויימת של הפרוטוקול). בגרסה זאת ההתנהגות הזאת נכונה לכל סוגי האובדנים. החסרון פה הוא שמצב של ACK כפול (כפי שאמרנו 3 פעמים ACK כפול גורם לשליחה מידית של החבילה פעם נוספת)
מעיד על כך שהרשת לא בהכרח כזאת עמוסה שצריך להוריד את הcwnd ל 1 שוב.

ב) אם האובדן הוא כתוצאה מ Timeout ההתנהגות היה כמו בסעיף א. אם האובדן הוא במצב של ACK כפול אז נקטין גם את ה cwnd וגם את ה sst להיות חצי מ cwnd. וממשיכים לעבור ב AIMD. בגדרת TCP Reno עובדים בשיטה הזו.

Pasted image 20240209000111.png

Fast Recovery

המנגנון הזה נועד למנוע מצב שאין חבילות באוויר כי מחכים ליותר מדי ACK כפולים.
מצב זה יכול לקרות אם החבילה הראשונה מבין כל החבילות שיכלנו לשלוח בהתאם לcwnd הלכה לאיבוד. זה אומר שעל כל החבילות הבאות אנחנו אמורים לקבל ACK כפול על החבילה הזאת. כפי שאמרנו בחבילה השלישית אנחנו מקטינים את החלון בחצי וכעת צריך לחכות ל ACK המצטבר על כל החבילות כדי להמשיך לשלוח חבילות חדשות. Fast Recovery מונע את זה בשיטה פשוטה: על כל ACK כפול הוא מגדיל את ה cwnd באחד. כלומר אם התקבלו 3 ACK כפולים אנחנו נקטין את החלון בחצי כפי שאמרנו ונוסיף לו 3. על כל ACK כפול נוסף נגדיר ב1 את החלון. הרעיון פה הוא שאנחנו כבר יודעים שנקבל ACK כפולים למרות ששלחנו כבר את החבילה ברגע שהגיעו רק 3 ושאין צורך לחכות לכולם כדי להמשיך לשלוח סגמנטים חדשים.

Pasted image 20240209002215.png

דוגמאות

תחת ההנחה שאין fast recovery נרצה למצוא את פרקי הזמן שבהם TCP נמצא ב slow start וב congestion avoidance
Pasted image 20240209112332.png
נשים לב שיש שני צירי x שאחד מהם זה הסיבובי שליחה והשני זה הcwnd size (מציג בצורה מדויקת את ציר ה y)

קל לראות זאת פשוט מחפשים היכן הגידול הוא מעריכי והיכן הגידול הוא ליניארי. לפי ציר הזמן זה בעצם מ 16 ומ 2326 מדובר בss וב 616 וגם 1722 מדובר ב ca. כמו כן אפשר לראות שהאובדן בסיבוב 16 מתרחש בגלל 3 ack כפולים והאובדן בסיבוב 23 קרה בגלל timeout.
נתונים נוספים שאפשר להוציא מהגרף הוא שערך ה tts ההתחלתי הוא 32 , בסיבוב מספר 18 ערכו הוא 21 והחל מסיבוב 23 ערכו הוא 13.

השהיות

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

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

נעזר בהגדרות שהשתמשנו בהם קודם :

Screenshot 2024-01-20 at 14.15.04.png

קצב שידור: כמה ביטים בשניה ניתן לשדר? (bps)
קצת התפשטות: כמה מטרים בשניה מצליח לעבור האות (mps)

נגדיר

dE2E=dtran+dprop+dq+dproc

מתקיים:
a) השהיית השידור היא dtran=LR כאשר L זה כמות הביטים ו R זה קצב השידור בשנייה.
b) השהיית ההתפשטות היא dprop=ds
- כמה זמן לוקח לביט בודד לעבור מרחק של d מטרים בקצב התפשטות של s מטרים בשנייה.
c) dq היא השהיית התור
- כמה זמן חבילה ממתינה בתור לפני שהיא משודרת הלאה. נשים לב שהשהייה זו היא פונקציה של קצב השידור של הנתב אל הערוץ וכמות החבילות בתור.
d) dproc היא השהיית העיבוד
- כמה זמן לוקח לטפל בחבילה ולהעביר אותה אל התור המתאים.

תזכורת

1B=8b1KB=103B1MB=106B1GB=109B

Pasted image 20240410161025.png

שאלה 1

לקוח הממוקם בניו יורק ניגש לשרת הממוקם בלונדון כדי להוריד קובץ. נתון שהמרחק מהלקוח לשרת הוא 2500KM , קצב ההתפשטות הוא 250,000km/s , קצב השידור הוא 8Mbps וגודל הקובץ הוא 1MB.

כמה זמן יקח להעביר את הקובץ מהשרת ללקוח, בהנחה שאין עוד תעבורה ורשת ואין השהיית עיבוד כלומר dproc=dq=0 .

אם כן , מתקיים ש dE2E=dt+dp , עם הנתונים שחישבנו נוכל לקבל אותם :
dt=LR=1MB8Mbs=8106b8106b=1sec כמו כן מתקיים ש dp=ds=2500250,000=0.01sec

Screenshot 2024-01-20 at 14.49.45.png|350

סך הכל נקבל dE2E=0.01+1=1.01s. נשים לב שחשוב להמיר את היחידות מידה כפי שעשינו, כי בדוגמה שלנו מדובר ב byte per second אל מול bits per seconds.

שאלה 2 - השהיות בשיטת store and forward

Screenshot 2024-01-20 at 15.28.56.png|350

נניח כי הראוטר עובד בשיטת store and forward כלומר רק כאשר הביט האחרון מגיע לראוטר הוא שולח אותו ללקוח.

Screenshot 2024-01-20 at 15.32.18.png|360

אם כן מתקיים :

dE2E=dtransSR+dpropSR+dtransRC+dpropRC

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

dtransSR=LR=1MB8Mbs=1dpropSR=ds=5000km250,000kms=0.02secdtransRC=LR=1MB800kbs=8106b800103bs=10secdpropRC=ds=250km250,000kms=103sec

מציבים ומקבלים dE2E=1+0.02+10+103=11.021sec .

השהיות בשיטת Pipeline

במצב זה מתקיים dE2E=d1+d2+d3 . כאשר:
a) d1 סך כל ההשהיות שלוקח לחבילה הראשונה עד שהיא מגיעה לערוץ האיטי ביותר.
b) d2 סך כל ההשהיות בערוץ האיטי ביותר
c)d3 היא סך כל ההשהיות שלוקח לחבילה האחרונה עד שהיא מגיעה ליעד

Screenshot 2024-01-20 at 15.58.17.png
נסתכל על הדוגמה למעלה שבה רוצים להעביר פקטה בשיטת pipeline שמחלקת את הפקטות ל4 פקטות קטנות.

בשלב הראשון אפשר לראות שאנחנו שולחים את הפקטות ולכל אחת לוקח 0.5sec להגיע לראוטר בשידור ובהתפשטות של 1sec כלומר הביט האחרון של פקטה יוצא אחרי שניה אחת.

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

Pasted image 20240120161215.png
נשים לב שהיכן שהשהיית השידור איטית ביותר, הזמן של כל השידורים האחרים נכלל בתוכו

Screenshot 2024-01-20 at 16.17.44.png

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

שאלה 3

Screenshot 2024-01-20 at 16.23.26.png
הבחנה: הקובץ מגיע מהשרת ולכן המסלול הוא שרת->נתב->לקוח

חישוב זריז יראה שאנחנו שולחים 1MB=8106b8106b8104b=100packets.
כעת, נמצא את הערוץ הכי איטי
- הערוץ בין הלקוח לראוטר הוא הערוץ עם קצב השידור האיטי ביותר 800kbps .

לכן צריך לחשב:
1. כמה זמן לוקח עד שהחבילה הראשונה מגיעה לנתב
2. כמה זמן לוקח עד שהמידע עובר בערוץ בין הנתב ללקוח

נחשב כמה זמן לקח לחבילה הראשונה עד שהגיעה לערוץ האיטי ביותר,
נקבל ש dt+dp=81048106+51062.5108=3102sec
כעת, נחשב כמה זמן לוקח לשדר את כל החבילות בערוץ הכי איטי dt=10081048105=10sec .
לסיום, נחשב את ההתפשטות של חבילה בודדת בערוץ האיטי dp=2.51052.5108=103sec

נסכום ונקבל 10.031 . נשים לב שלא חסכנו הרבה זמן בגלל הערוץ האיטי שהוסיף לנו 10sec

Info

לכאורה כדאי פשוט לשלוח את המידע בחבילות בגודל של ביט בודד כי אנחנו נדרשים לחכות פחות עבור הפקטה הראשונה, כלומר ככל שיש יותר פקטות קטנות נראה כי הקנס על התעבורה עד לטווח האיטי קטנה משמעותית
Pasted image 20240120171917.png|450
אבל זה לא באמת המצב , מהסיבה השפוטה שלכל פקטה ששולחים מתווספים headers והמחיר של זה יכול להיות כבד בהתאם לגודל ה header. למשל אם חילקנו ל 100 חבילות בפועל אנחנו מחשבים את זמן השידור של 101 חבילות כי מסתכלים על זמן השידור של החבילה הראשונה עד שהיא מגיעה לערוץ האיטי ביותר ואז מסתכלים על כל החבילות בערוץ האיטי ביותר. אם נגדיר שלכל חבילה כזאת יש גם header בגודל 40B נקבל שכאשר נוסיף את זמן השידור של התחיליות עצמן בנוסף למה שחישבנו נקבל : 140B8106bps+10040B8105bps=40.04ms אם מוסיפים את ה40 מילישניות האלה נקבל 10.07104 זמן שלא משנה בצורה חסרת תקדים את הזמן הקודם. אבל אם נחלק ל 10000 חבילות נקבל שהתקורה על העברת התחיליות הוא 4.00004s שזה זמן משמעותי מאוד ונקבל זמן גדול בהרבה (באזור 14).

השהיית תור

נתבונן בדוגמה הבאה:
מחשבים A,B שולחים חבילות למחשב D דרך נתב R . כאשר חבילה מ A מגיעה ל R יש כבר 3 חבילות מ B שממתינות בבאפר ועוד חצי חבילה שמשודרת ל D.
נוסיף גם שהתור בנתב עובד בצורת FIFO וכל החבילות הן בגודל 1000B עם קצב שידור של 1Mbps .

נרצה לחשב את השהיית התור עבור החבילה של A.

החבילה שהגיעה מ A צריכה לחכות שכל החבילות שלפניה ישודרו כלומר:

dq=3.5103B106bps=0.028sec

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

נסתכל על עוד דוגמה:
אם A,B שולחים כל אחד ל D קובץ 10Mb דרך נתב C.
נתון שקצב השידור הוא 10Mbps ו dp=1ms=103sec . כמו כן, גודל החבילה המקסימלית הוא 1KB ושניתן להתעלם מתחיליות.

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

ראשית נבין מהו המספר המינימלי של חבילות שיש לשלוח: 10Mb10KB=1250
נשים לב שמשך הזמן הכולל הוא החל מתחילת שידור החבילה הראשונה (שני המחשבים מתחילים באותו זמן) ועד שהחבילה האחרונה מגיעה ל D. הזמן שלוקח לשדר חבילה אחת : dt=LR=1KB10Mbps=8103b107bps=0.0008sec

סך הכל אנחנו נשדר מ C ל D בידיוק 2501 חבילות כי יש את התקורה של החבילה הראשונה שאמורה להגיע לנתב לפני שהוא מתחיל לשדר. כמו כן יש לקחת בחשבון את 2 השהיות ההתפשטות גם של החבילה הראשונה עד שהיא מגיע לנתב וגם של החבילה האחרונה עד שהיא מגיע ל D. סך הכל

dE2E=2501dt+2dp=25010.0008+2103=2.0028s

כעת כדי לקבוע מהו גודל הבאפר המינימלי:
כיוון שקצב השידור בכל הערוצים שווה, בזמן ש C מספיק לשדר חבילה אחת הוא קיבל שתי חבילות חדשות. לכן, אם הוא קיבל 20Mb אזי הוא מספיק בזמן הזה להעביר חצי מזה והשאר מחכה בבאפר, כלומר גודל הבאפר צריך להיות 10Mb.

Packet Fragmentation

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

Screenshot 2024-02-10 at 15.44.27.png
יש לנו datagram בשכבת הרשת בגודל 4000 בתים, אם נוריד את הheader זה יוצא 3980 בתים. ה MTU הוא 1500 בתים ואם נוריד את הip header זה אומר שבפרגמנטצייה נוכל להעביר כל פעם מידע בגודל 1480.

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

נסביר את הדגלים:
fragFlag - דגל שקובע שיש עוד פרגמנטים אחריי לפי סדר החלוקה ולא סדר ההגעה.

ID- מזהה שמאפשר לדעת שכל הפרגמנטים קשורים לאותו data

offset- השדה Fragment Offset (13 בתים) משמש לציון מיקום ההתחלה של הנתונים בפרגמנט ביחס לתחילת הנתונים בחבילה המקורית. מידע זה משמש להרכבה מחדש של הנתונים מכל הפרגמנטים (בין אם הם מגיעים בסדר ובין אם לא). בפרגמנט הראשון ההיסט הוא 0 שכן הנתונים בחבילה זו מתחילים באותו מקום כמו הנתונים בחבילה המקורית (בהתחלה). בפרגמנטים הבאים, הערך הוא ההיסט של הנתונים שהפרגמנט מכיל מתחילת הנתונים בפרגמנט הראשון (היסט 0), ב-8 בתים 'בלוקים'. אם מנה המכילה 800 בתים של נתונים מפוצלת לשני מקטעים שווים הנושאים 400 בתים של נתונים, היסט הפרגמנט של הפרגמנט הראשון הוא 0, של הפרגמנט השני 50 (400/8). ערך ההיסט חייב להיות המספר של בלוקים של 8 בתים של נתונים, כלומר הנתונים בפרגמנט הקודם חייבים להיות כפולה של 8 בתים. הקטע האחרון יכול לשאת נתונים שאינם כפולה של 8 בתים מכיוון שלא יהיה קטע נוסף עם היסט שחייב לעמוד ב'כלל' זה.

Pasted image 20240211113637.png

הפעולה הזאת מבוצעת בNetwork Layer, הפרגמנטציה נעשית בעקבות datagram size שגדול יותר מה MTU של הערוץ שבו המידע אמור לעבור. שכבת התעבורה מנסה לטפל בזה (לפחות אם משתמשים ב TCP), לכן חשוב להכיר את התהליך הזה כבר בשכבה הזו.

ניתן לראות בתמונה שבעצם מתבצע שכפול של כלל ה headers לתתי הפקטת. כמו כן ניתן לראות שיש payload שמתווסף לheaders כתוצאה מהחלוקה הזאת. למשל offset של המידע fragflag שקובע האם יש עוד פרגמנטים בדרך.

Path MTU Discovery

לקוחות יודעים את ה MTU של הערוץ שלהם. לקוחות שאיתם יש תקשורת יודעים לדווח על ה MTU של הערוץ שלהם. אבל, המסלול מורכב מערוצים נוספים שם ה MTU יכול להיות שונה.

TCP מנסה להמנע מפרגמנטציה לאורך המסלול באמצעות Path MTU Discovery שעובד בצורה הבאה:
א) שולחים חבילה, ע״פ מגבלות הגודל הידועות, כאשר הדגל Dont fragment דולק.
ב) כאשר החבילה תגיע לערוץ בו ה MTU קטן מהחבילה, במקום פרגמנטציה, תשלח בחזרה הודעת הודעת שגיאה בעזרת הפרוטוקול ICMP שאומרת שהחבילה גדולה מדי, ומה צריך להיות גודל החבילה המקס׳.
ג) השולח יעדכן בהתאם את גודל ה MSS וה MTU וישלח בהתאם.
ד) במידה וגודל זה עדיין גדול מדי לערוצים אחרים במסלול, חוזרים חלילה.

דוגמה:

Screenshot 2024-02-22 at 19.26.30.png
Screenshot 2024-02-22 at 19.30.44.png
הערוץ האיטי הוא בין שני הנתבים ולכן ערוץ זה הוא זה שמכתיב את הקצב בו החבילות יתקבלו אצל המקבל.
Screenshot 2024-02-22 at 20.49.01.png|350

בגלל שהביט do not frag כבוי אז המידע יעבור פרגמנטייה בערוץ בין שני הנתבים. הראוטר האמצעי מקבל 1530B אבל ה MTU שלו הוא 530B.

לכן הוא לוקח את את ה1530 מחסיר מהם 20 של header IP וקיבלנו 1510. בגלל שה MTU הוא 530 נגדיר פרגמנט בגודל 510 ונוסיף לו 20 בתים של תחילית IP כדי לקבל את הפרגמנט הראשון. גם את הפרגמנט הבא נעשה באותה צורה וכעת עבור הפרגמנט האחרון נשאר לנו 490 בתים של מידע + 20 בתים של תחילים שכבת הרשת (ip) נקבל 510 בתים.

כעת לשאלה השנייה, נניח שההסתברות לכך שהנתב השני זורק כל חבילה שמגיעה היא p.

ההסתברות שהדגימה הראשונה תגיע בהצלחה ליישום של בוב היא ההסתברות שכל שלושת הפרגמנטים יגיעו לבוב ויורכבו בהצלחה בשכבת האפליקציה. כלומר אסור לאף פרגמנט ליפול בדרך ולכן ההסתברות (1p)3 .

היחס בין גודל החלון וקצב השידור

נרצה להבין את היחס בין גודל החלון שמשדרים וקצב השידור לבין RTT כלומר: WLR ? RTT (הנוסחה הזאת בעצם מהווה את זמן השידור של חלון אחד).

a) אם מתקיים WLR<RTT אז סיימנו לשדר לפני שהגיע ack ולכן אנחנו מחכים ולא משדרים עד שיגיע ה ack .
Pasted image 20240223133819.png|300
ניתן לראות שכאשר מצב זה מתקיים ישנו זמן מת (המשולש באדום) עד שניתן לשלוח את החלון הבא.

b) אם מתקיים WLR>RTT אזי ה ack על החבילה הראשונה מגיע תוך כדי שידור החלון, ולכן כאשר אנחנו מסיימים לשדר את החלון הראשון ניתן מיד להתחיל לשדר את החלון השני באופן רצוף ללא בזבוז זמן מת.

Pasted image 20240223133306.png|300
כאשר היחס גדול יותר ניתן לראות שמתקבל ack על החבילה הראשונה לפני שסיימנו לשדר.

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

כלומר, החל מאיזה חלון ה RTT כבר לא גדול יותר מזמן השידור של החלון. עם זאת, מכיוון שהRTT מכין בתוכו את זמן השידור של החבילה הראשונה, אם חיבור הTCP מתחיל מחלון בגודל חבילה אחת, בחלון הראשון תמיד ה RTT יהיה גדול יותר מזמן השידור של החלון.

החל מאיזה חלון הRTT כבר לא גדול יותר מזמן השידור של החלון?

נשים לב שכשאשר אנחנו ב Slow start אנחנו משדרים בחלון ה i כמות של 2i1 חבילות. למשל בחלון ה 1 משדרים חבילה אחת ובחלון ה 3 משדרים 4 חבילות וכן הלאה.

נרצה אם למצוא את הi שיקיים

2i1dtranRTT

כאשר באי שיוויון זה הנעלם היחיד הוא i כי ניתן יהיה לחשב את השהיית השידור ואת ה RTT. עד החלון ה i אנחנו משדרים לפי WLR<RTT והחל מהחלון ה i משדרים לפי WLR>RTT.

דוגמה:
Screenshot 2024-02-23 at 13.11.37.png

נרצה לשלוח 122000150040=83.5684 חבילות, כאשר החבילה האחרונה אינה מלאה והיא בגודל 820B . משך הזמן שלוקח לשלוח חבילה אחת

dt+dp=1460B+40B12Mbps+12ms=13ms

משך הזמן שלוקח לשלוח את החבילה האחרונה הוא

dt+dp=820B+40B12Mbps+12ms=12.573ms

משך הזמן שלוקח לשלוח ack :

dt+dp=40B12Mbps+12ms=12.027ms

סך הכל קיבלנו

RTT=13ms+12.027ms=25.027msRTTlast=12.573ms+12.027ms=24.6ms

ב stop and wait ייקח לנו

8325.027ms+24.6ms=2.101841sec

נשים לב שכאן ברור ש WLR<RTT כי שולחים את החבילה הבאה רק כאשר ה ack מגיע על החבילה הקודמת.

Screenshot 2024-02-23 at 13.23.18.png
במצב זה כבר יש לבדוק את היחס בין שידור החלון לבין הRTT

WLR=81500B12Mbps=8ms

מתקיים ש WLR<RTT לכן:

11RTT+21460B+40B12Mbps+820B+40B12Mbps=0.2779

הסיבה ששמנו 11 היא בגלל שהיחס הנ״ל הוא > אז מתקיים שנשלח 10 סיבובים מלאים של 8 חבילות בנפרד ועוד סיבוב אחד של 4 חבילות (לא משפיע על ה RTT שנמדד לפי החבילה הראשונה). לאחר החישוב הזה מוסיפים את זמן השידור של כל החבילות בחלון האחרון חוץ מהחבילה הראשונה שנספרה בRTT ה 11 . החבילה האחרונה נזכיר היא קטנה יותר משאר החבילות.

Silly Window Syndrome

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

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

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

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

בצד השולח, נשתמש באלגוריתם Nagle
- אם אין מידע שמחכים ל ack עליו, שלח את המידע שהאפליקצייה ביקשה.
- אם יש מידע שמחכים לack עליו נעכב בקשות שליחה של האפליקציה אם הן קטנות מדי עד שנקבל את ה ack או עד שיצטבר מספיק מידע, כלומר בגודל של לפחות MSS .
נשים לב שהאלגוריתם הזה לא עובד טוב עם delayed ack algorithm.

דוגמה:
Screenshot 2024-02-23 at 18.00.18.png

במקרה הכללי , אם הקצב שבו מגיע המידע הוא R והקצב שבו קוראת האפליקציה מהבאפר הוא O וגודל הבאפר בפועל הוא W אזי הנוסחה עבור גודל החלון ה אגרסיבי אותו ניתן לפרסם היא W1OR .

נשים לב שהעיקר פה הוא השהיית ההתפשטות ולכן גם אן נתעלם משאר ההשהיות נקבל שאם השולח ישלח מידע בגודל הבאפר וימתין עד לקבלת תשובה, נקבל שקצב העברת המידע הוא 200000BRTT=200000B0.4s=500KBps.

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

  2. נציב בנוסחה 200000B18Mbps10Mbps=106B . גודל חלון כזה יתן לנו קצב של 1060.4=2.5MBps .