פרוטוקול TCP

פרוטוקול TCP במילים פשוטות

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

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

פרולוג

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

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

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

בעיה ראשונה – שינוע מידע רב

רשת האינטרנט, ופרוטוקול IP בפרט, מאפשרים העברת כמויות קטנות בלבד של מידע בכל "משלוח". בכדי להעביר מידע רב, יש לחלק אותו למנות קטנות ולשלוח כל אחת מהן בנפרד. פרוטוקול TCP, בדומה לפרוטוקולים אחרים, מחלק את המידע הנשלח לחבילות הנקראות פקטות (packets), ותפקידו של הצד המקבל הוא להרכיב אותן חזרה לכדי זרם מידע רציף. תכונה זאת של פרוטוקול TCP אינה ייחודית, ורוב הפרוטוקולים בשכבת התעבורה ומטה מחלקים גם הם את המידע לפקטות, כך ש-TCP אינו מיוחד בהקשר זה.

בעיה שניה – סדר ההגעה

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

מכיוון שמטרתו של פרוטוקול TCP הוא לאפשר העברת זרם מידע רציף, שיתקבל בדיוק כפי שנשלח, יש צורך לסדר את הפקטות שהתקבלו לפי הסדר בו נשלחו. הפתרון: הצמדת מספר סידורי רץ (sequence number) לכל פקטה1 ע"י הצד השולח, כך שהצד המקבל יוכל לשחזר את הסדר המקורי שלהן, גם אם הן הגיעו בסדר שונה מהסדר בו נשלחו. לדוגמה, אם צד א' שלח 3 פקטות, וצד ב' קיבל את פקטות 2 ו-3 בלבד, הוא יחכה עד להגעת פקטה מספר 1 ורק לאחר מכן ירכיב את המידע ויעביר אותו הלאה.

בעיה שלישית – פקטות אבודות

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

גם לבעיה זאת פרוטוקול TCP מספק מנגון התמודדות פשוט. זוכרים את המספר הסידורי? אז פרט לסידור הפקטות הוא משמש גם לביצוע "אישור הגעה"; כאשר החבילה הגיעה ליעדה, הצד המקבל שולח חזרה את המספר הסידורי שלה, ובכך מודיע לשולח שהחבילה התקבלה, אישור זה נקרא acknowledgement, או בקיצור ack. במידה והשולח לא מקבל ack תוך זמן מוגדר מראש משליחת החבילה, הוא פשוט שולח אותה שוב.

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

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

שאלה מתבקשת היא – האין כל ה-acks הללו מוסיפים תקורה רבה מדי על הקו? התשובה המיידית היא – לא, זאת מכיוון שה-acks לרוב אינם נשלחים בתור פקטות בפני עצמן! פרוטוקול TCP מנצל את העובדה שהחיבור הוא דו כיווני (full duplex, שני הצדדים שולחים מידע אחד לשני), כדי לצרף את האישורים לפקטות נתונים שגם ככה היו אמורות להישלח בכיוון ההפוך. תכונה זאת נקראת piggybacking. אך אם צד אחד רק מקבל מידע ולא שולח כלום? במקרה זה הוא יחכה כדי "לצבור" פקטות, ולאחר שעבר זמן מסויים הוא ישלח אישור אחד שיכסה את כל הפקטות בבת אחת.

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

בעיה רביעית – הצב והארנב

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

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

הנה סרטון נחמד הממחיש את העיקרון:

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

בעיה חמישית – מישהו שומע אותי?

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

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

חבר: "הלו?"
אני: "היי, מה שלומך?"
חבר: "מצויין, מה איתך?"

תהליך זה נקרא "לחיצת יד תלת שלבית" (Three-way handshake) והוא השלב הראשון בכל חיבור TCP. שמותיהן המקצועיים של שלושת ההודעות הנשלחות הם SYN, SYN-ACK, ACK. לחיצת יד זאת מבטיחה (בסבירות גבוהה) ששני הצדדים מוכנים לדבר אחד עם השני, ומסמלת את תחילת חיבור ה-TCP. בנוסף, כחלק מהתהליך שני הצדדים מסכמים ביניהם איך תתבצע התקשורת – למשל מהו המספר הסידורי של הפקטה הראשונה, מהו גודל החלון ההתחלתי ועוד.

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

אני: "אני סבבה, ואתה?"

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

בעיה שישית – אתה עוד שם?

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

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

בעיה שביעית – "את-ה מק-קו-טע"

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

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

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

בעיה שמינית – מספר חיבורים במקביל

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

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

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

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

לסיכום

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

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

 

1 המספר הסידורי משקף למעשה כל בית (byte) של נתונים ולא כל חבילה. למשל, אם המספר הסידור של החבילה הוא 100, והיא מכילה 50 בתים, מספר החבילה הבאה יהיה 150.

2 תגובות בנושא “פרוטוקול TCP במילים פשוטות”

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים