לדלג לתוכן

9.4 - חיבור TCP ותנועת הנתונים - פתרון

פתרון ההבחנה בין שכבות

פתרון ראשון - שלוש ראיות

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

// Server, after validating request:
stream.write_all(&(request.len() as u64).to_be_bytes())?;

// Client, after sending the request:
let mut acknowledgement = [0_u8; 8];
client.read_exact(&mut acknowledgement)?;
let processed = u64::from_be_bytes(acknowledgement);
assert_eq!(processed, 5);

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

פתרון שני - מדידה של לחץ חוזר

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

client.set_write_timeout(Some(std::time::Duration::from_millis(200)))?;
let block = [0_u8; 64 * 1024];
let mut accepted = 0_u64;
loop {
    match client.write(&block) {
        Ok(0) => return Err(io::Error::new(io::ErrorKind::WriteZero, "no progress")),
        Ok(count) => accepted += count as u64,
        Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
        Err(error) if matches!(error.kind(), io::ErrorKind::WouldBlock |
                                            io::ErrorKind::TimedOut) => break,
        Err(error) => return Err(error),
    }
}
println!("locally accepted before timeout: {accepted}");

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

פתרון שלישי - בדיקת התוצאה

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

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