פתרון חיבור וזרם של מידע¶
פתרון התרגיל הראשון¶
נוסיף לקובץ ההרצאה את הפונקציה הבאה ונשתמש בה בצד הלקוח במקום send_frame. היא משתמשת באותו framing; רק חלוקת הקריאות השתנתה.
#include <algorithm>
void send_fragmented(SOCKET s, std::string_view text) {
if (text.empty() || text.size() > 1024)
throw std::runtime_error("invalid frame length");
std::uint32_t length = htonl(static_cast<std::uint32_t>(text.size()));
const char* header = reinterpret_cast<const char*>(&length);
for (int i = 0; i < static_cast<int>(sizeof(length)); ++i)
send_all(s, header + i, 1);
for (std::size_t offset = 0; offset < text.size();) {
auto chunk = (std::min)(std::size_t{3}, text.size() - offset);
send_all(s, text.data() + offset, static_cast<int>(chunk));
offset += chunk;
}
}
המערכת יכולה לאחד מידע לפני שהשרת קורא אותו, והשרת יכול לקרוא פחות מהמידע שכבר הגיע. לכן נבדוק את תוכן ההודעה ואת הגבולות, ולא את מספר הrecvים. השימוש ב(std::min) מונע התנגשות עם macro בשם min בסביבות headers שבהן הוא מוגדר; אפשר גם להגדיר NOMINMAX לפני headers של ווינדוס.
פתרון התרגיל השני¶
בניתוק נקי לפני header, הקריאה הראשונה מחזירה אפס והשרת מסיים בלי עבודה. בשליחת שני bytes מתוך ארבעה, read_exact כבר התקדם ולכן סוף הזרם הוא header קטוע. כאשר הheader שלם אבל הגוף קצר, הכשל שייך לגוף המובטח.
הקטע הבא מחליף את השליחה הרגילה בצד הלקוח לצורך המקרה השלישי. לאחריו אפשר לסגור את התוכנית; אין לצפות לתשובת echo תקינה.
std::uint32_t declared = htonl(10);
send_all(endpoint.value, reinterpret_cast<const char*>(&declared), sizeof(declared));
send_all(endpoint.value, "abc", 3);
end_sending(endpoint.value);
השרת לא אמור לעבד את abc כהודעה באורך שלושה, משום שהפרוטוקול כבר הבטיח עשרה. טיפול כזה היה משנה את משמעות הקלט לפי תזמון הניתוק.
פתרון התרגיל השלישי¶
השרת ממתין בexpect_eof, והלקוח ממתין לheader של תשובה. כל אחד מצפה שהשני יתקדם קודם. בסוף אחת ההמתנות תיכשל לפי timeout או מצב החיבור, אבל עצם קיום timeout אינו תיקון לפרוטוקול.
בפרוטוקול מרובה הודעות נשתמש בlength ובסוג הודעה כדי לזהות סוף בקשה. השרת יענה אחרי frame שלם בלי לחכות לFIN. נוסיף מזהה בקשה כדי לקשר תשובות, ונגדיר אם מותר כמה בקשות פעילות במקביל ובאיזה סדר מותר להחזיר אותן.
לסיום אפשר להגדיר הודעת close שאחריה לא נשלחות בקשות חדשות, או half-close ברמת השיחה אחרי כל הבקשות. בשני המקרים נמשיך לקרוא את התשובות שכבר התחייבנו לקבל עד סיום מתאים. הנקודה היא שסוף הודעה וסוף כיוון TCP הם שני אירועים שונים בפרוטוקול.