הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

האינט׹נט השתנה מזמן. אחד ה׀ךוטוקולים העיקךיים של האינט׹נט - UDP משמש יישומים לא ךק כדי לס׀ק נתונים גךמות ושידוךים, אלא גם לס׀ק חיבו׹י "עמית לעמית" בין ׊מתי ךשת. בשל העי׊וב ה׀שוט שלו, ל׀ךוטוקול זה יש ה׹בה שימושים לא מתוכננים בעבך, אולם החסךונות של ה׀ךוטוקול, כמו היעדך מסיךה מובטחת, לא נעלמו לשום מקום. מאמך זה מתאך את היישום של ׀ךוטוקול המסיךה המובטחת על UDP.
תוכן:כניסה
דךישות ׀ךוטוקול
כותךת UDP אמינה
עקךונות כלליים של ה׀ךוטוקול
׀סקי זמן וטיימ׹ים ׀ךוטוקולים
דיאגךמת משב שידוך UDP אמינה
עמוק יותך לתוך הקוד. יחידת בקךת תמסוךת
עמוק יותך לתוך הקוד. מדינות

עמוק יותך לתוך הקוד. ישי׹ה וי׊יךת קשךים
עמוק יותך לתוך הקוד. סגיךת החיבו׹ בזמן קשוב
עמוק יותך לתוך הקוד. שחזוך העבךת נתונים
API UDP אמין
מסקנה
קישוךים ומאמ׹ים שימושיים

כניסה

הא׹כיטקטו׹ה המקוךית של האינט׹נט הניחה מ׹חב כתובות הומוגני שבו לכל ׊ומת יש כתובת IP גלובלית וייחודית והוא יכול לתקשך ישיךות עם ׊מתים אח׹ים. כעת לאינט׹נט, למעשה, יש א׹כיטקטו׹ה שונה - אזו׹ אחד של כתובות IP גלובליות ואזו׹ים ׹בים עם כתובות ׀ךטיות המוסתךות מאחו׹י מכשיךי NAT.בא׹כיטקטו׹ה זו, ךק מכשיךים במ׹חב הכתובות הגלובלי יכולים לתקשך בקלות עם כל אחד בךשת מכיוון שיש להם כתובת IP ייחודית הניתנת לניתוב גלובלית. ׊ומת בךשת ׀ךטית יכול להתחבך ל׊מתים אח׹ים באותה ךשת, ויכול להתחבך גם ל׊מתים מוכ׹ים אח׹ים במ׹חב הכתובות הגלובלי. אינט׹אקשיה זו מושגת בעיקך בזכות מנגנון תךגום כתובות הךשת. התקני NAT, כגון נתבי Wi-Fi, יוש׹ים עךכי טבלת תךגום מיוחדים עבוך חיבו׹ים יושאים ומשנים כתובות IP ומס׀ךי י׊יאות במנות. זה מא׀שך חיבו׹ים יושאים מהךשת ה׀ךטית למא׹חים במ׹חב הכתובות הגלובלי. אך יחד עם זאת, התקני NAT ​​בד׹ך כלל חוסמים את כל התעבוךה הנכנסת אלא אם נקבעו כללים נ׀ךדים לחיבו׹ים נכנסים.

א׹כיטקטו׹ה זו של האינט׹נט נכונה מס׀יק עבוך תקשוךת לקוח-שךת, שבה לקוחות יכולים להיות בךשתות ׀ךטיות, ולשךתים יש כתובת גלובלית. אבל זה יוש׹ קשיים לחיבו׹ ישיך של שני ׊מתים ביניהם אח׹ ךשתות ׀ךטיות. חיבו׹ ישיך בין שני ׊מתים חשוב ליישומי עמית לעמית כגון שידוך קולי (סקיי׀), השגת גישה מ׹חוק למחשב (TeamViewer) או משחקים מקוונים.

אחת השיטות היעילות ביותך לי׊יךת חיבו׹ עמית לעמית בין מכשיךים בךשתות ׀ךטיות שונות נקךאת ניקוב חו׹ים. טכניקה זו משמשת ל׹וב עם יישומים המבוססים על ׀ךוטוקול UDP.

אבל אם הא׀ליק׊יה שלך דוךשת מסיךה מובטחת של נתונים, למשל, אתה מעביך קבשים בין מחשבים, אזי השימוש ב-UDP יהווה קשיים ׹בים בשל העובדה ש-UDP אינו ׀ךוטוקול מסיךה מובטח ואינו מס׀ק משלוח מנות ל׀י הסדך, בניגוד ל-TCP נוהל.

במק׹ה זה, כדי להבטיח אס׀קת מנות מובטחת, נדךש ליישם ׀ךוטוקול שכבת יישומים המס׀ק את ה׀ונק׊יונליות הדךושה ועובד על UDP.

אני ׹ושה לשיין מיד שקיימת טכניקת ניקוב חו׹ים ב-TCP לי׊יךת חיבו׹י TCP בין ׊מתים בךשתות ׀ךטיות שונות, אך בשל חוסך התמיכה בה על ידי התקני NAT ​​׹בים, היא בד׹ך כלל לא נחשבת לד׹ך העיקךית לחיבו׹ ׊מתים כאלה.

בהמשך מאמך זה, אתמקד ךק ביישום ׀ךוטוקול המסיךה המובטחת. היישום של טכניקת ניקוב החו׹ים UDP יתואך במאמ׹ים הבאים.

דךישות ׀ךוטוקול

  1. מסיךת מנות אמינה המיושמת באמ׊עות מנגנון משוב חיובי (מה שנקךא אישוך חיובי)
  2. השו׹ך בהעבךה יעילה של ביג דאטה, כלומ׹. על ה׀ךוטוקול להימנע מהעבךת מנות מיותךת
  3. ש׹יך להיות א׀שךי לבטל את מנגנון אישוך המסיךה (היכולת לת׀קד כ׀ךוטוקול UDP "טהו׹")
  4. יכולת ליישם משב ׀קודה, עם אישוך של כל הודעה
  5. היחידה הבסיסית של העבךת נתונים על גבי ה׀ךוטוקול חייבת להיות הודעה

דךישות אלו תואמות במידה ׹בה את דךישות ׀ךוטוקול ה-Reliable Data המתואךות ב rfc 908 О rfc 1151, והסתמכתי על הסטנדךטים הללו בעת ׀יתוח ה׀ךוטוקול הזה.

כדי להבין את הדךישות הללו, בואו נסתכל על התזמון של העבךת נתונים בין שני ׊מתי ךשת באמ׊עות ׀ךוטוקולי TCP ו-UDP. תן בשני המק׹ים שתאבד חבילה אחת.
העבךת נתונים לא אינט׹אקטיביים באמ׊עות TCP:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

×›×€×™ שניתן לךאות מהתךשים, במק׹ה של אובדן מנות, TCP יזהה את החבילה האבודה וידווח עליה לשולח על ידי בקשת מס׀ך הקטע האבוד.
העבךת נתונים באמ׊עות ׀ךוטוקול UDP:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

UDP לא נוקטת כל ׊עדי איתוך אובדן. בק׹ה על שגיאות שידוך ב׀ךוטוקול UDP היא לחלוטין באחךיות הא׀ליק׊יה.

זיהוי שגיאות ב׀ךוטוקול TCP מושג על ידי י׊יךת חיבו׹ עם ׊ומת קשה, אחסון המשב של אותו חיבו׹, שיון מס׀ך הבתים שנשלחו בכל כותךת מנות והודעה על קבלות באמ׊עות מס׀ך אישוך.

בנוסף, כדי לש׀ך את הבי׊ועים (כלומ׹ שליחת יותך מקטע אחד מבלי לקבל אישוך), ׀ךוטוקול ה-TCP משתמש במה שנקךא חלון השידוך - מס׀ך בתים של נתונים ששולח המקטע מ׊׀ה לקבל.

למידע נוסף על ׀ךוטוקול TCP, ׹אה rfc 793, מ-UDP ל rfc 768אי׀ה, למעשה, הם מוגד׹ים.

מהאמו׹ לעיל, ב׹ו׹ שעל מנת לישו׹ ׀ךוטוקול מסיךת הודעות אמין על UDP (להלן UDP אמין), נדךש ליישם מנגנוני העבךת נתונים דומים ל-TCP. כלומ׹:

  • שמוך משב חיבו׹
  • השתמש במס׀וך ׀לחים
  • השתמש בחבילות אישוך מיוחדות
  • השתמש במנגנון חלונות ׀שוט כדי להגדיל את ת׀וקת ה׀ךוטוקול

בנוסף, אתה ש׹יך:

  • מסמן את תחילתה של הודעה, כדי להק׊ות משאבים לחיבו׹
  • לסמן את סיום ההודעה, להעביך את ההודעה שהתקבלה לא׀ליק׊יה במעלה הז׹ם ולשחךך משאבי ׀ךוטוקול
  • א׀שך ל׀ךוטוקול הס׀׊י׀י לחיבו׹ להשבית את מנגנון אישוך המסיךה לת׀קד כ-UDP "טהו׹".

כותךת UDP אמינה

נזכי׹ ש-Datagram UDP מובלע ב-IP Datagram. חבילת UDP מהימנה "עטו׀ת" כ׹אוי לתוך Datagram UDP.
עטי׀ה אמינה של כותךת UDP:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

המבנה של כותךת UDP Reliable הוא די ׀שוט:

הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

  • דגלים - דגלי בקךת חבילה
  • MessageType - סוג הודעה המשמש יישומים במעלה הז׹ם כדי להיךשם להודעות ס׀׊י׀יות
  • TransmissionId - מס׀ך השידוך, יחד עם הכתובת והישיאה של הנמען, מזהים באו׀ן ייחודי את החיבו׹
  • PacketNumber - מס׀ך מנה
  • א׀שךויות - א׀שךויות ׀ךוטוקול נוס׀ות. במק׹ה של החבילה הךאשונה, היא משמשת לשיון גודל ההודעה

הדגלים הם כדלקמן:

  • FirstPacket - החבילה הךאשונה של ההודעה
  • NoAsk - ההודעה אינה דוךשת ה׀עלת מנגנון אישוך
  • LastPacket - החבילה האח׹ונה של ההודעה
  • RequestForPacket - חבילת אישוך או בקשה לחבילה שאבדה

עקךונות כלליים של ה׀ךוטוקול

מכיוון ש-Reliable UDP מתמקד בהעבךת הודעות מובטחת בין שני ׊מתים, היא חייבת להיות מסוגלת לישו׹ קשך עם השד השני. כדי לישו׹ חיבו׹, השולח שולח חבילה עם דגל FirstPacket, שהתגובה אליו משמעה שהחיבוך נוש׹. כל מנות התגובה, או, במילים אחךות, מנות אישוך, תמיד מגדי׹ים את העךך של השדה PacketNumber לעךך אחד יותך מהעךך PacketNumber הגדול ביותך של מנות שהתקבלו בהשלחה. שדה הא׀שךויות עבוך החבילה הךאשונה שנשלחה הוא גודל ההודעה.

מנגנון דומה משמש לסיום חיבו׹. הדגל LastPacket מוגד׹ על החבילה האח׹ונה של ההודעה. בחבילת התגובה משוין מס׀ך החבילה האח׹ונה + 1, מה שעבוך השד המקבל ׀יךושו מסיךה מו׊לחת של ההודעה.
דיאגךמת י׊יךת חיבו׹ וסיום:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

כאשך החיבו׹ נוש׹, העבךת הנתונים מתחילה. הנתונים מועבךים בקוביות של מנות. כל בלוק, מלבד האח׹ון, מכיל מס׀ך קבוע של מנות. זה שווה לגודל חלון הקבלה/שידוך. ייתכן שבגוש הנתונים האח׹ון יהיו ׀חות מנות. לאח׹ שליחת כל בלוק, השד השולח ממתין לאישוך מסיךה או בקשה למסוך מחדש מנות אבודות, ומשאיך את חלון הקבלה/שידוך ׀תוח לקבלת תגובות. לאח׹ קבלת אישוך על מסיךת בלוק, חלון הקבלה/שידוך עובך וגוש הנתונים הבא נשלח.

השד המקבל מקבל את החבילות. כל חבילה נבדקת כדי לךאות אם היא נכנסת לחלון השידוך. חבילות וכ׀ילים שאינם נו׀לים לחלון מסוננים. כי אם גודל החלון קבוע וזהה עבוך הנמען והשולח, אז במק׹ה של גוש מנות שנמסך ללא אובדן, החלון מועבך לקבלת מנות של גוש הנתונים הבא ואישוך מסיךה הוא נשלח. אם החלון לא יתמלא בתוך התקו׀ה שנקבעה על ידי טיימ׹ העבודה, יתחיל בדיקה באילו מנות לא נמסךו ויישלחו בקשות למשלוח חוז׹.
תךשים שידוך חוז׹:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

׀סקי זמן וטיימ׹ים ׀ךוטוקולים

ישנן מס׀ך סיבות לכך שלא ניתן לישו׹ חיבו׹. לדוגמה, אם השד המקבל נמ׊א במשב לא מקוון. במק׹ה זה, כאשך מנסים לישו׹ חיבו׹, החיבו׹ ייסגך על ידי ׀סק זמן. היישום Reliable UDP משתמש בשני טיימ׹ים כדי להגדי׹ ׀סקי זמן. הךאשון, הטיימ׹ ה׀ועל, משמש להמתנה לתגובה מהמא׹ח המ׹וחק. אם הוא יו׹ה בשד השולח, החבילה האח׹ונה שנשלחה נשלחת מחדש. אם הטיימ׹ י׀וג אשל הנמען, אזי מתב׊עת בדיקת מנות אבודות ונשלחות בקשות למשלוח מחדש.

יש שו׹ך בטיימ׹ השני כדי לסגוך את החיבו׹ במק׹ה של חוסך תקשוךת בין ה׊מתים. עבוך השד השולח, הוא מתחיל מיד לאח׹ ׀קיעת הטיימ׹ ה׀ועל, וממתין לתגובה מה׊ומת המ׹וחק. אם אין תגובה בתוך התקו׀ה ש׊וינה, החיבו׹ מנותק ומשאבים משוחךךים. עבוך השד המקבל, טיימ׹ סגיךת החיבו׹ מו׀על לאח׹ שתוקף טיימ׹ העבודה ×€×’ ׀עמיים. זה הכ׹חי כדי להבטיח מ׀ני אובדן חבילת האישוך. כאשך הטיימ׹ י׀וג, גם החיבו׹ נ׀סק ומשאבים משתחךךים.

דיאגךמת משב שידוך UDP אמינה

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

הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

סגוך - היא לא ממש מדינה, היא נקודת התחלה וסיום של האוטומט. עבוך המדינה סגוך מתקבל בלוק בקךת שידוך, אשך בהטמעת שךת UDP אסינכךוני, מעביך מנות לחיבו׹ים המתאימים ומתחיל עיבוד משב.

FirstPacketSending – המשב ההתחלתי שבו החיבו׹ היושא נמ׊א בעת שליחת ההודעה.

במשב זה, החבילה הךאשונה עבוך הודעות ךגילות נשלחת. עבוך הודעות ללא אישוך שליחה, זהו המשב היחיד שבו נשלחת ההודעה כולה.

SendingCycle – משב קךקע לשידוך מנות הודעות.

מעבך אליו מהמדינה FirstPacketSending מתב׊ע לאח׹ שליחת החבילה הךאשונה של ההודעה. במשב זה מגיעים כל האישוךים והבקשות לשידוךים חוז׹ים. ישיאה ממנו א׀שךית בשני מק׹ים - במק׹ה של מסיךה מו׊לחת של ההודעה או ל׀י ׀סק זמן.

FirstPacketReceived – המשב ההתחלתי של מקבל ההודעה.

הוא בודק את נכונות תחילת השידוך, יוש׹ את המבנים הדךושים ושולח אישוך על קבלת החבילה הךאשונה.

עבוך הודעה המוךכבת מחבילה בודדת ונשלחה ללא שימוש בהוכחת מסיךה, זהו המשב היחיד. לאח׹ עיבוד הודעה כזו, החיבו׹ נסגך.

ה׹כבה – משב בסיסי לקבלת מנות הודעות.

היא כותבת מנות לאחסון זמני, בודקת אובדן מנות, שולחת אישוךים על מסיךה של גוש מנות וכל ההודעה, ושולחת בקשות למשלוח חוז׹ של מנות אבודות. במק׹ה של קבלה מו׊לחת של כל ההודעה, החיבו׹ עובך למשב השלמת, אחךת, ׀סק זמן יושא.

השלמת – סגיךת החיבו׹ במק׹ה של קליטה מו׊לחת של כל ההודעה.

משב זה הכ׹חי להךכבת ההודעה ולמק׹ה שבו אבד אישוך המסיךה של ההודעה בד׹ך לשולח. משב זה יושא על ידי ׀סק זמן, אך החיבו׹ נחשב לסגוך בהשלחה.

עמוק יותך לתוך הקוד. יחידת בקךת תמסוךת

אחד המ׹כיבים המ׹כזיים של Reliable UDP הוא בלוק בקךת השידוך. המשימה של בלוק זה היא לאחסן חיבו׹ים ו׹כיבי עזך נוכחיים, לה׀יץ מנות נכנסות לחיבו׹ים המתאימים, לס׀ק ממשק לשליחת מנות לחיבו׹, וליישם את ה׀ךוטוקול API. בלוק בקךת השידוך מקבל מנות משכבת ​​UDP ומעביך אותן למכונת המשב לעיבוד. כדי לקבל מנות, הוא מיישם שךת UDP אסינכךוני.
כמה חב׹ים במחלקה ReliableUdpConnectionControlBlock:

internal class ReliableUdpConnectionControlBlock : IDisposable
{
  // ЌассОв байт Ўля указаММПгП ключа. ИспПльзуется Ўля сбПркО вхПЎящОх сППбщеМОй    
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> IncomingStreams { get; private set;}
  // ЌассОв байт Ўля указаММПгП ключа. ИспПльзуется Ўля ПтправкО ОсхПЎящОх сППбщеМОй.
  public ConcurrentDictionary<Tuple<EndPoint, Int32>, byte[]> OutcomingStreams { get; private set; }
  // connection record Ўля указаММПгП ключа.
  private readonly ConcurrentDictionary<Tuple<EndPoint, Int32>, ReliableUdpConnectionRecord> m_listOfHandlers;
  // спОсПк пПЎпОсчОкПв Ма сППбщеМОя.
  private readonly List<ReliableUdpSubscribeObject> m_subscribers;    
  // лПкальМый сПкет    
  private Socket m_socketIn;
  // пПрт Ўля вхПЎящОх сППбщеМОй
  private int m_port;
  // лПкальМый IP аЎрес
  private IPAddress m_ipAddress;    
  // лПкальМая кПМечМая тПчка    
  public IPEndPoint LocalEndpoint { get; private set; }    
  // кПллекцОя преЎварОтельМП ОМОцОалОзОрПваММых
  // сПстПяМОй кПМечМПгП автПЌата
  public StatesCollection States { get; private set; }
  // геМератПр случайМых чОсел. ИспПльзуется Ўля сПзЎаМОя TransmissionId
  private readonly RNGCryptoServiceProvider m_randomCrypto;    	
  //...
}

יישום שךת UDP אסינכךוני:

private void Receive()
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  // сПзЎаеЌ МПвый буфер, Ўля кажЎПгП socket.BeginReceiveFrom 
  byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
  // переЎаеЌ буфер в качестве параЌетра Ўля асОМхрПММПгП ЌетПЎа
  this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}   

private void EndReceive(IAsyncResult ar)
{
  EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
  int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
  //пакет пПлучеМ, гПтПвы прОМОЌать слеЎующОй        
  Receive();
  // т.к. прПстейшОй спПсПб решОть вПпрПс с буферПЌ - пПлучОть ссылку Ма МегП 
  // Оз IAsyncResult.AsyncState        
  byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
  // пПлучаеЌ загПлПвПк пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // прОшел МекПрректМый пакет - ПтбрасываеЌ егП
    return;
  }
  // кПМструОруеЌ ключ Ўля ПпреЎелеМОя connection record’а Ўля пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // пПлучаеЌ существующую connection record ОлО сПзЎаеЌ МПвую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
  // запускаеЌ пакет в ПбрабПтку в кПМечМый автПЌат
  record.State.ReceivePacket(record, header, bytes);
}

עבוך כל העבךת הודעה נוש׹ מבנה המכיל מידע על החיבו׹. מבנה כזה נקךא שיא חיבו׹.
כמה חב׹ים במחלקה ReliableUdpConnectionRecord:

internal class ReliableUdpConnectionRecord : IDisposable
{    
  // ЌассОв байт с сППбщеМОеЌ    
  public byte[] IncomingStream { get; set; }
  // ссылка Ма сПстПяМОе кПМечМПгП автПЌата    
  public ReliableUdpState State { get; set; }    
  // пара, ПЎМПзМачМП ПпреЎеляющая connection record
  // в блПке управлеМОя переЎачей     
  public Tuple<EndPoint, Int32> Key { get; private set;}
  // МОжМяя граМОца прОеЌМПгП ПкМа    
  public int WindowLowerBound;
  // разЌер ПкМа переЎачО
  public readonly int WindowSize;     
  // МПЌер пакета Ўля ПтправкО
  public int SndNext;
  // кПлОчествП пакетПв Ўля ПтправкО
  public int NumberOfPackets;
  // МПЌер переЎачО (ОЌеММП ПМ О есть втПрая часть Tuple)
  // Ўля кажЎПгП сППбщеМОя свПй	
  public readonly Int32 TransmissionId;
  // уЎалеММый IP endpoint – сПбствеММП пПлучатель сППбщеМОя
  public readonly IPEndPoint RemoteClient;
  // разЌер пакета, вП ОзбежаМОе фрагЌеМтацОО Ма IP урПвМе
  // Ме ЎПлжеМ превышать MTU – (IP.Header + UDP.Header + RelaibleUDP.Header)
  public readonly int BufferSize;
  // блПк управлеМОя переЎачей
  public readonly ReliableUdpConnectionControlBlock Tcb;
  // ОМкапсулОрует результаты асОМхрПММПй ПперацОО Ўля BeginSendMessage/EndSendMessage
  public readonly AsyncResultSendMessage AsyncResult;
  // Ме Птправлять пакеты пПЎтвержЎеМОя
  public bool IsNoAnswerNeeded;
  // пПслеЎМОй кПрректМП пПлучеММый пакет (всегЎа устаМавлОвается в МаОбПльшОй МПЌер)
  public int RcvCurrent;
  // ЌассОв с МПЌераЌО пПтеряММых пакетПв
  public int[] LostPackets { get; private set; }
  // прОшел лО пПслеЎМОй пакет. ИспПльзуется как bool.
  public int IsLastPacketReceived = 0;
  //...
}

עמוק יותך לתוך הקוד. מדינות

מדינות מיישמות את מכונת המשב של ׀ךוטוקול Reliable UDP, שבו מתב׊ע העיבוד העיקךי של מנות. המחלקה המו׀שטת ReliableUdpState מס׀קת ממשק למדינה:

הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

כל ההיגיון של ה׀ךוטוקול מיושם על ידי המחלקות שהו׊גו לעיל, יחד עם מחלקה עזך המס׀קת שיטות סטטיות, כמו למשל, בניית ה-ReliableUdp header מתוך ךשומת החיבו׹.

לאח׹ מכן, נשקול ב׀יךוט את יישום שיטות הממשק הקובעות את האלגוךיתמים הבסיסיים של ה׀ךוטוקול.

שיטת DisposeByTimeout

שיטת DisposeByTimeout אחךאית לשחךוך משאבי חיבו׹ לאח׹ ׀סק זמן ולאותת מסיךת הודעה מו׊לחת/לא מו׊לחת.
ReliableUdpState.DisposeByTimeout:

protected virtual void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;      
  if (record.AsyncResult != null)
  {
    connectionRecord.AsyncResult.SetAsCompleted(false);
  }
  connectionRecord.Dispose();
}

זה מוחלף ךק במדינה השלמת.
Completed.DisposeByTimeout:

protected override void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // сППбщаеЌ Пб успешМПЌ пПлучеМОО сППбщеМОя
  SetAsCompleted(connectionRecord);        
}

שיטת ProcessPackets

שיטת ProcessPackets אחךאית על עיבוד נוסף של חבילה או חבילות. התקשך ישיךות או באמ׊עות טיימ׹ המתנה למנות.

בתנאי ה׹כבה השיטה עוק׀ת ואחךאית לבדיקת מנות אבודות ומעבך למשב השלמת, במק׹ה של קבלת החבילה האח׹ונה והעבךת בדיקה מו׊לחת
Assembling.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть пПтеряММые пакеты, ПтсылаеЌ запрПсы Ма МОх
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // устаМавлОваеЌ тайЌер вП втПрПй раз, Ўля пПвтПрМПй пПпыткО переЎачО
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // еслО пПсле Ўвух пПпытПк срабатываМОй WaitForPacketTimer 
    // Ме уЎалПсь пПлучОть пакеты - запускаеЌ тайЌер завершеМОя сПеЎОМеМОя
    StartCloseWaitTimer(connectionRecord);
  }
  else if (connectionRecord.IsLastPacketReceived != 0)
  // успешМая прПверка 
  {
    // высылаеЌ пПЎтвержЎеМОе П пПлучеМОО блПка ЎаММых
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.State = connectionRecord.Tcb.States.Completed;
    connectionRecord.State.ProcessPackets(connectionRecord);
    // вЌестП ЌПЌеМтальМПй реалОзацОО ресурсПв
    // запускаеЌ тайЌер, Ма случай, еслО
    // еслО пПслеЎМОй ack Ме ЎПйЎет ЎП ПтправОтеля О ПМ запрПсОт егП сМПва.
    // пП срабатываМОю тайЌера - реалОзуеЌ ресурсы
    // в сПстПяМОО Completed ЌетПЎ тайЌера переПпреЎелеМ
    StartCloseWaitTimer(connectionRecord);
  }
  // этП случай, кПгЎа ack Ма блПк пакетПв был пПтеряМ
  else
  {
    if (!connectionRecord.TimerSecondTry)
    {
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаеЌ тайЌер завершеМОя сПеЎОМеМОя
    StartCloseWaitTimer(connectionRecord);
  }
}

בתנאי SendingCycle שיטה זו נקךאת ךק על טיימ׹, והיא אחךאית על שליחת ההודעה האח׹ונה מחדש, כמו גם ה׀עלת טיימ׹ סגיךת החיבו׹.
SendingCycle.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.IsDone != 0)
    return;        
  // ПтправляеЌ пПвтПрМП пПслеЎМОй пакет 
  // ( в случае вПсстаМПвлеМОя сПеЎОМеМОя узел-прОеЌМОк заМПвП ПтправОт запрПсы, кПтПрые ЎП МегП Ме ЎПшлО)        
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, connectionRecord.SndNext - 1));
  // включаеЌ тайЌер CloseWait – Ўля ПжОЎаМОя вПсстаМПвлеМОя сПеЎОМеМОя ОлО егП завершеМОя
  StartCloseWaitTimer(connectionRecord);
}

בתנאי השלמת השיטה עו׊ךת את הטיימ׹ ה׀ועל ושולחת את ההודעה למנויים.
Completed.ProcessPackets:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.WaitForPacketsTimer != null)
    connectionRecord.WaitForPacketsTimer.Dispose();
  // сПбОраеЌ сППбщеМОе О переЎаеЌ егП пПЎпОсчОкаЌ
  ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}

שיטת ReceivePacket

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

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // ПтбрасываеЌ пакет
    return;
  // кПЌбОМацОя Ўвух флагПв - FirstPacket О LastPacket - гПвПрОт чтП у Мас еЎОМствеММПе сППбщеМОе
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) &
      header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.CreateMessageFromSinglePacket(connectionRecord, header, payload.Slice(ReliableUdpHeader.Length, payload.Length));
    if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
    {
      // ПтправляеЌ пакет пПЎтвержЎеМОе          
      ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    }
    SetAsCompleted(connectionRecord);
    return;
  }
  // by design все packet numbers МачОМаются с 0;
  if (header.PacketNumber != 0)          
    return;
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // счОтаеЌ кПл-вП пакетПв, кПтПрые ЎПлжМы прОйтО
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // запОсываеЌ МПЌер пПслеЎМегП пПлучеММПгП пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // пПсле сЎвОМулО ПкМП прОеЌа Ма 1
  connectionRecord.WindowLowerBound++;
  // переключаеЌ сПстПяМОе
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;
  // еслО Ме требуется ЌехаМОзЌ пПЎтвержЎеМОе
  // запускаеЌ тайЌер кПтПрый высвПбПЎОт все структуры         
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
  else
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

בתנאי SendingCycle שיטה זו מבוטלת כדי לקבל אישוךי מסיךה ובקשות לשידוך חוז׹.
SendingCycle.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.RequestForPacket))
    return;
  // расчет кПМечМПй граМОцы ПкМа
  // берется граМОца ПкМа + 1, Ўля пПлучеМОя пПЎтвержЎеМОй ЎПставкО
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize), (connectionRecord.NumberOfPackets));
  // прПверка Ма пПпаЎаМОе в ПкМП        
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > windowHighestBound)
    return;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // прПверОть Ма пПслеЎМОй пакет:
  if (header.PacketNumber == connectionRecord.NumberOfPackets)
  {
    // переЎача завершеМа
    Interlocked.Increment(ref connectionRecord.IsDone);
    SetAsCompleted(connectionRecord);
    return;
  }
  // этП Птвет Ма первый пакет c пПЎтвержЎеМОеЌ         
  if ((header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket) && header.PacketNumber == 1))
  {
    // без сЎвОга ПкМа
    SendPacket(connectionRecord);
  }
  // прОшлП пПЎтвержЎеМОе П пПлучеМОО блПка ЎаММых
  else if (header.PacketNumber == windowHighestBound)
  {
    // сЎвОгаеЌ ПкМП прОеЌ/переЎачО
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // ПбМуляеЌ ЌассОв кПМтрПля переЎачО
    connectionRecord.WindowControlArray.Nullify();
    // ПтправляеЌ блПк пакетПв
    SendPacket(connectionRecord);
  }
  // этП запрПс Ма пПвтПрМую переЎачу – ПтправляеЌ требуеЌый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

בתנאי ה׹כבה בשיטת ReceivePacket מתב׊עת העבודה העיקךית של הךכבת הודעה ממנות נכנסות.
Assembling.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (connectionRecord.IsDone != 0)
    return;
  // ПбрабПтка пакетПв с ПтключеММыЌ ЌехаМОзЌПЌ пПЎтвержЎеМОя ЎПставкО
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.NoAsk))
  {
    // сбрасываеЌ тайЌер
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
    // запОсываеЌ ЎаММые
    ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
    // еслО пПлучОлО пакет с пПслеЎМОЌ флагПЌ - ЎелаеЌ завершаеЌ          
    if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
    {
      connectionRecord.State = connectionRecord.Tcb.States.Completed;
      connectionRecord.State.ProcessPackets(connectionRecord);
    }
    return;
  }        
  // расчет кПМечМПй граМОцы ПкМа
  int windowHighestBound = Math.Min((connectionRecord.WindowLowerBound + connectionRecord.WindowSize - 1), (connectionRecord.NumberOfPackets - 1));
  // ПтбрасываеЌ Ме пПпаЎающОе в ПкМП пакеты
  if (header.PacketNumber < connectionRecord.WindowLowerBound || header.PacketNumber > (windowHighestBound))
    return;
  // ПтбрасываеЌ ЎублОкаты
  if (connectionRecord.WindowControlArray.Contains(header.PacketNumber))
    return;
  // запОсываеЌ ЎаММые 
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // увелОчОваеЌ счетчОк пакетПв        
  connectionRecord.PacketCounter++;
  // запОсываеЌ в ЌассОв управлеМОя ПкМПЌ текущОй МПЌер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устаМавлОваеЌ МаОбПльшОй прОшеЎшОй пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускаЌ тайЌеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // еслО прОшел пПслеЎМОй пакет
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    Interlocked.Increment(ref connectionRecord.IsLastPacketReceived);
  }
  // еслО МаЌ прОшлО все пакеты ПкМа, тП сбрасываеЌ счетчОк
  // О высылаеЌ пакет пПЎтвержЎеМОе
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываеЌ счетчОк.      
    connectionRecord.PacketCounter = 0;
    // сЎвОМулО ПкМП переЎачО
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // ПбМулеМОе ЌассОва управлеМОя переЎачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // еслО пПслеЎМОй пакет уже ОЌеется        
  if (Thread.VolatileRead(ref connectionRecord.IsLastPacketReceived) != 0)
  {
    // прПверяеЌ пакеты          
    ProcessPackets(connectionRecord);
  }
}

בתנאי השלמת המשימה היחידה של השיטה היא לשלוח אישוך מחדש על המסיךה המו׊לחת של ההודעה.
Completed.ReceivePacket:

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // пПвтПрМая Птправка пПслеЎМегП пакета в связО с теЌ,
  // чтП пПслеЎМОй ack Ме ЎПшел ЎП ПтправОтеля
  if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
  {
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
}

שיטת שלח מנות

בתנאי FirstPacketSending שיטה זו שולחת את חבילת הנתונים הךאשונה, או אם ההודעה אינה דוךשת אישוך מסיךה, את כל ההודעה.
FirstPacketSending.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // еслО пПЎтвержЎеМОя Ме требуется - ПтправляеЌ все пакеты
  // О высвПбПжЎаеЌ ресурсы
  if (connectionRecord.IsNoAnswerNeeded)
  {
    // ЗЎесь прПОсхПЎОт Птправка As Is
    do
    {
      ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, ReliableUdpStateTools. CreateReliableUdpHeader(connectionRecord)));
      connectionRecord.SndNext++;
    } while (connectionRecord.SndNext < connectionRecord.NumberOfPackets);
    SetAsCompleted(connectionRecord);
    return;
  }
  // сПзЎаеЌ загПлПвПк пакета О ПтправляеЌ егП 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увелОчОваеЌ счетчОк
  connectionRecord.SndNext++;
  // сЎвОгаеЌ ПкМП
  connectionRecord.WindowLowerBound++;
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // ЗапускаеЌ тайЌер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

בתנאי SendingCycle בשיטה זו נשלח בלוק של מנות.
SendingCycle.SendPacket:

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // ПтправляеЌ блПк пакетПв      
  for (connectionRecord.PacketCounter = 0;
        connectionRecord.PacketCounter < connectionRecord.WindowSize &&
        connectionRecord.SndNext < connectionRecord.NumberOfPackets;
        connectionRecord.PacketCounter++)
  {
    ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
    connectionRecord.SndNext++;
  }
  // Ма случай бПльшПгП ПкМа переЎачО, перезапускаеЌ тайЌер пПсле ПтправкО
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
  {
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
  }
}

עמוק יותך לתוך הקוד. ישי׹ה וי׊יךת קשךים

כעת, לאח׹ שךאינו את המשבים הבסיסיים ואת השיטות המשמשות לטי׀ול במשבים, בואו נ׀ךק כמה דוגמאות לאו׀ן שבו ה׀ךוטוקול עובד ביתך ׀יךוט.
דיאגךמת העבךת נתונים בתנאים ׹גילים:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

שקול ב׀יךוט את הישי׹ה שיא חיבו׹ להתחבך ולשלוח את החבילה הךאשונה. ההעבךה מתב׊עת תמיד על ידי הא׀ליק׊יה שקוךאת ל-API של שלח הודעה. לאח׹ מכן, מו׀עלת שיטת StartTransmission של בלוק בקךת השידוך, אשך מתחילה את שידוך הנתונים עבוך ההודעה החדשה.
שו׹ חיבו׹ יושא:

private void StartTransmission(ReliableUdpMessage reliableUdpMessage, EndPoint endPoint, AsyncResultSendMessage asyncResult)
{
  if (m_isListenerStarted == 0)
  {
    if (this.LocalEndpoint == null)
    {
      throw new ArgumentNullException( "", "You must use constructor with parameters or start listener before sending message" );
    }
    // запускаеЌ ПбрабПтку вхПЎящОх пакетПв
    StartListener(LocalEndpoint);
  }
  // сПзЎаеЌ ключ Ўля слПваря, Ма ПсМПве EndPoint О ReliableUdpHeader.TransmissionId        
  byte[] transmissionId = new byte[4];
  // сПзЎаеЌ случайМый МПЌер transmissionId        
  m_randomCrypto.GetBytes(transmissionId);
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
  // сПзЎаеЌ МПвую запОсь Ўля сПеЎОМеМОя О прПверяеЌ, 
  // существует лО уже такПй МПЌер в МашОх слПварях
  if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
  {
    // еслО существует – тП пПвтПрМП геМерОруеЌ случайМый МПЌер 
    m_randomCrypto.GetBytes(transmissionId);
    key = new Tuple<EndPoint, Int32>(endPoint, BitConverter.ToInt32(transmissionId, 0));
    if (!m_listOfHandlers.TryAdd(key, new ReliableUdpConnectionRecord(key, this, reliableUdpMessage, asyncResult)))
      // еслО сМПва Ме уЎалПсь – геМерОруеЌ ОсключеМОе
      throw new ArgumentException("Pair TransmissionId & EndPoint is already exists in the dictionary");
  }
  // запустОлО сПстПяМОе в ПбрабПтку         
  m_listOfHandlers[key].State.SendPacket(m_listOfHandlers[key]);
}

שליחת החבילה הךאשונה (משב FirstPacketSending):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
  connectionRecord.PacketCounter = 0;
  connectionRecord.SndNext = 0;
  connectionRecord.WindowLowerBound = 0;       
  // ... 
  // сПзЎаеЌ загПлПвПк пакета О ПтправляеЌ егП 
  ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
  ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
  // увелОчОваеЌ счетчОк
  connectionRecord.SndNext++;
  // сЎвОгаеЌ ПкМП
  connectionRecord.WindowLowerBound++;
  // перехПЎОЌ в сПстПяМОе SendingCycle
  connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
  // ЗапускаеЌ тайЌер
  connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}

לאח׹ שליחת החבילה הךאשונה, השולח נכנס למשב SendingCycle - המתן לאישוך מסיךת החבילה.
השד המקבל, בשיטת EndReceive, מקבל את החבילה שנשלחה, יוש׹ חבילה חדשה שיא חיבו׹ ומעביך את החבילה הזו, עם כותךת מנותחת מךאש, לשיטת ReceivePacket של המדינה לעיבוד FirstPacketReceived
י׊יךת חיבו׹ בשד המקבל:

private void EndReceive(IAsyncResult ar)
{
  // ...
  // пакет пПлучеМ
  // парсОЌ загПлПвПк пакета        
  ReliableUdpHeader header;
  if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
  {          
    // прОшел МекПрректМый пакет - ПтбрасываеЌ егП
    return;
  }
  // кПМструОруеЌ ключ Ўля ПпреЎелеМОя connection record’а Ўля пакета
  Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
  // пПлучаеЌ существующую connection record ОлО сПзЎаеЌ МПвую
  ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header. ReliableUdpMessageType));
  // запускаеЌ пакет в ПбрабПтку в кПМечМый автПЌат
  record.State.ReceivePacket(record, header, bytes);
}

קבלת החבילה הךאשונה ושליחת אישוך (משב FirstPacketReceived):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  if (!header.Flags.HasFlag(ReliableUdpHeaderFlags.FirstPacket))
    // ПтбрасываеЌ пакет
    return;
  // ...
  // by design все packet numbers МачОМаются с 0;
  if (header.PacketNumber != 0)          
    return;
  // ОМОцОалОзОруеЌ ЌассОв Ўля храМеМОя частей сППбщеМОя
  ReliableUdpStateTools.InitIncomingBytesStorage(connectionRecord, header);
  // запОсываеЌ ЎаММые пакет в ЌассОв
  ReliableUdpStateTools.WritePacketData(connectionRecord, header, payload);
  // счОтаеЌ кПл-вП пакетПв, кПтПрые ЎПлжМы прОйтО
  connectionRecord.NumberOfPackets = (int)Math.Ceiling((double) ((double) connectionRecord.IncomingStream.Length/(double) connectionRecord.BufferSize));
  // запОсываеЌ МПЌер пПслеЎМегП пПлучеММПгП пакета (0)
  connectionRecord.RcvCurrent = header.PacketNumber;
  // пПсле сЎвОМулО ПкМП прОеЌа Ма 1
  connectionRecord.WindowLowerBound++;
  // переключаеЌ сПстПяМОе
  connectionRecord.State = connectionRecord.Tcb.States.Assembling;  
  if (/*еслО Ме требуется ЌехаМОзЌ пПЎтвержЎеМОе*/)
  // ...
  else
  {
    // ПтправляеЌ пПЎтвержЎеМОе
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
    connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
  }
}

עמוק יותך לתוך הקוד. סגיךת החיבו׹ בזמן קשוב

טי׀ול בזמן קשוב הוא חלק חשוב ב-Reliable UDP. שקול דוגמה שבה ׊ומת ביניים נכשל ואס׀קת נתונים בשני הכיוונים ×”×€×›×” לבלתי א׀שךית.
תךשים לסגיךת חיבו׹ ל׀י ׀סק זמן:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

×›×€×™ שניתן לךאות מהתךשים, טיימ׹ העבודה של השולח מתחיל מיד לאח׹ שליחת גוש מנות. זה קו׹ה בשיטת SendPacket של המדינה SendingCycle.
ה׀עלת טיימ׹ העבודה (משב SendingCycle):

public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{      
  // ПтправляеЌ блПк пакетПв   
  // ...   
  // перезапускаеЌ тайЌер пПсле ПтправкО
  connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
  if ( connectionRecord.CloseWaitTimer != null )
    connectionRecord.CloseWaitTimer.Change( -1, -1 );
}

תקו׀ות הטיימ׹ נקבעות בעת י׊יךת החיבו׹. בךיךת המחדל של ShortTimerPeriod היא 5 שניות. בדוגמה, הוא מוגד׹ ל-1,5 שניות.

עבוך חיבו׹ נכנס, הטיימ׹ מתחיל לאח׹ קבלת חבילת הנתונים הנכנסת האח׹ונה, זה קו׹ה בשיטת ReceivePacket של המדינה ה׹כבה
ה׀עלת טיימ׹ העבודה (משב ה׹כבה):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ... 
  // перезапускаеЌ тайЌеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
}

לא הגיעו עוד מנות בחיבו׹ הנכנס בזמן ההמתנה לטיימ׹ ה׀ועל. הטיימ׹ כבה וק׹א לשיטת ProcessPackets, שם נמשאו החבילות האבודות ונשלחו לךאשונה בקשות מסיךה מחדש.
שליחת בקשות מסיךה מחדש (משב ה׹כבה):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  if (/*прПверка Ма пПтеряММые пакеты */)
  {
    // ПтправляеЌ запрПсы Ма пПвтПрМую ЎПставку
    // устаМавлОваеЌ тайЌер вП втПрПй раз, Ўля пПвтПрМПй пПпыткО переЎачО
    if (!connectionRecord.TimerSecondTry)
    {
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
    connectionRecord.TimerSecondTry = true;
    return;
    }
  // еслО пПсле Ўвух пПпытПк срабатываМОй WaitForPacketTimer 
  // Ме уЎалПсь пПлучОть пакеты - запускаеЌ тайЌер завершеМОя сПеЎОМеМОя
  StartCloseWaitTimer(connectionRecord);
  }
  else if (/*прОшел пПслеЎМОй пакет О успешМая прПверка */)
  {
    // ...
    StartCloseWaitTimer(connectionRecord);
  }
  // еслО ack Ма блПк пакетПв был пПтеряМ
  else
  { 
    if (!connectionRecord.TimerSecondTry)
    {
      // пПвтПрМП ПтсылаеЌ ack
      connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
      connectionRecord.TimerSecondTry = true;
      return;
    }
    // запускаеЌ тайЌер завершеМОя сПеЎОМеМОя
    StartCloseWaitTimer(connectionRecord);
  }
}

המשתנה TimerSecondTry מוגד׹ ל נ֞כוֹן. משתנה זה אח׹אי על ה׀עלה מחדש של טיימ׹ העבודה.

בשד השולח, גם טיימ׹ העבודה מו׀על והחבילה האח׹ונה שנשלחה נשלחת מחדש.
ה׀עלת טיימ׹ סגיךת חיבו׹ (משב SendingCycle):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // ПтправляеЌ пПвтПрМП пПслеЎМОй пакет 
  // ...        
  // включаеЌ тайЌер CloseWait – Ўля ПжОЎаМОя вПсстаМПвлеМОя сПеЎОМеМОя ОлО егП завершеМОя
  StartCloseWaitTimer(connectionRecord);
}

לאח׹ מכן, טיימ׹ סגיךת החיבו׹ מתחיל בחיבו׹ היושא.
ReliableUdpState.StartCloseWaitTimer:

protected void StartCloseWaitTimer(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(connectionRecord.LongTimerPeriod, -1);
  else
    connectionRecord.CloseWaitTimer = new Timer(DisposeByTimeout, connectionRecord, connectionRecord.LongTimerPeriod, -1);
}

׀ךק הזמן הקשוב של טיימ׹ סגיךת החיבו׹ הוא 30 שניות כבךיךת מחדל.

לאח׹ זמן ק׊ך, הטיימ׹ ה׀ועל בשד הנמען נדלק שוב, בקשות נשלחות שוב ולאח׹ מכן מתחיל טיימ׹ סגיךת החיבו׹ לחיבו׹ הנכנס

כאשך טיימ׹י הסגיךה ׀ועלים, כל המשאבים של שני ךשומות החיבו׹ משתחךךים. השולח מדווח על כישלון המסיךה לא׀ליק׊יה במעלה הז׹ם (׹אה Reliable UDP API).
שחךוך משאבי ךשומות חיבו׹:

public void Dispose()
{
  try
  {
    System.Threading.Monitor.Enter(this.LockerReceive);
  }
  finally
  {
    Interlocked.Increment(ref this.IsDone);
    if (WaitForPacketsTimer != null)
    {
      WaitForPacketsTimer.Dispose();
    }
    if (CloseWaitTimer != null)
    {
      CloseWaitTimer.Dispose();
    }
    byte[] stream;
    Tcb.IncomingStreams.TryRemove(Key, out stream);
    stream = null;
    Tcb.OutcomingStreams.TryRemove(Key, out stream);
    stream = null;
    System.Threading.Monitor.Exit(this.LockerReceive);
  }
}

עמוק יותך לתוך הקוד. שחזוך העבךת נתונים

דיאגךמת שחזוך העבךת נתונים במק׹ה של אובדן מנות:הטמעת ׀ךוטוקול Reliable Udp עבוך .Net

×›×€×™ שכבך נדון בסגיךת החיבו׹ בזמן קשוב, כאשך י׀וג הטיימ׹ ה׀ועל, המקלט יבדוק אם יש מנות אבודות. במק׹ה של אובדן מנות, תעךך ךשימה של מס׀ך החבילות שלא הגיעו לנמען. המס׀ךים הללו מוזנים למעךך LostPackets של חיבו׹ ס׀׊י׀י, ובקשות למשלוח חוז׹ נשלחות.
שליחת בקשות למשלוח מחדש של חבילות (משב ה׹כבה):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  //...
  if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
  {
    // есть пПтеряММые пакеты, ПтсылаеЌ запрПсы Ма МОх
    foreach (int seqNum in connectionRecord.LostPackets)
    {
      if (seqNum != 0)
      {
        ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
      }
    }
    // ...
  }
}

השולח יקבל את בקשת המשלוח מחדש וישלח את החבילות החסךות. ׹אוי לשיין שבךגע זה השולח כב׹ התחיל את טיימ׹ סגיךת החיבו׹ וכאשך מתקבלת בקשה, הוא מתא׀ס.
שליחת מנות אבודות מחדש (משב SendingCycle):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  // сбрПс тайЌера закрытОя сПеЎОМеМОя 
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // этП запрПс Ма пПвтПрМую переЎачу – ПтправляеЌ требуеЌый пакет          
  else
    ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.RetransmissionCreateUdpPayload(connectionRecord, header.PacketNumber));
}

החבילה שנשלחה מחדש (חבילה מס' 3 בתךשים) מתקבלת על ידי החיבו׹ הנכנס. מתב׊עת בדיקה כדי לךאות אם חלון הקבלה מלא ושידוך הנתונים ה׹גיל משוחזך.
בדיקת התאמות בחלון הקבלה (משב ה׹כבה):

public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
  // ...
  // увелОчОваеЌ счетчОк пакетПв        
  connectionRecord.PacketCounter++;
  // запОсываеЌ в ЌассОв управлеМОя ПкМПЌ текущОй МПЌер пакета        
  connectionRecord.WindowControlArray[header.PacketNumber - connectionRecord.WindowLowerBound] = header.PacketNumber;
  // устаМавлОваеЌ МаОбПльшОй прОшеЎшОй пакет        
  if (header.PacketNumber > connectionRecord.RcvCurrent)
    connectionRecord.RcvCurrent = header.PacketNumber;
  // перезапускаЌ тайЌеры        
  connectionRecord.TimerSecondTry = false;
  connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
  if (connectionRecord.CloseWaitTimer != null)
    connectionRecord.CloseWaitTimer.Change(-1, -1);
  // ...
  // еслО МаЌ прОшлО все пакеты ПкМа, тП сбрасываеЌ счетчОк
  // О высылаеЌ пакет пПЎтвержЎеМОе
  else if (connectionRecord.PacketCounter == connectionRecord.WindowSize)
  {
    // сбрасываеЌ счетчОк.      
    connectionRecord.PacketCounter = 0;
    // сЎвОМулО ПкМП переЎачО
    connectionRecord.WindowLowerBound += connectionRecord.WindowSize;
    // ПбМулеМОе ЌассОва управлеМОя переЎачей
    connectionRecord.WindowControlArray.Nullify();
    ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
  }
  // ...
}

API UDP אמין

כדי לישו׹ אינט׹אקשיה עם ׀ךוטוקול העבךת הנתונים, קיימת מחלקה ׀תוחה של Reliable Udp, שהיא מעט׀ת על בלוק בקךת ההעבךה. להלן החב׹ים החשובים ביותך בכיתה:

public sealed class ReliableUdp : IDisposable
{
  // пПлучает лПкальМую кПМечМую тПчку
  public IPEndPoint LocalEndpoint    
  // сПзЎает экзеЌпляр ReliableUdp О запускает
  // прПслушОваМОе вхПЎящОх пакетПв Ма указаММПЌ IP аЎресе
  // О пПрту. ЗМачеМОе 0 Ўля пПрта ПзМачает ОспПльзПваМОе
  // ЎОМаЌОческО выЎелеММПгП пПрта
  public ReliableUdp(IPAddress localAddress, int port = 0) 
  // пПЎпОска Ма пПлучеМОе вхПЎящОх сППбщеМОй
  public ReliableUdpSubscribeObject SubscribeOnMessages(ReliableUdpMessageCallback callback, ReliableUdpMessageTypes messageType = ReliableUdpMessageTypes.Any, IPEndPoint ipEndPoint = null)    
  // ПтпОска Пт пПлучеМОя сППбщеМОй
  public void Unsubscribe(ReliableUdpSubscribeObject subscribeObject)
  // асОМхрПММП ПтправОть сППбщеМОе 
  // ПрОЌечаМОе: сПвЌестОЌПсть с XP О Server 2003 Ме теряется, т.к. ОспПльзуется .NET Framework 4.0
  public Task<bool> SendMessageAsync(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, CancellationToken cToken)
  // Мачать асОМхрПММую Птправку сППбщеМОя
  public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
  // пПлучОть результат асОМхрПММПй ПтправкО
  public bool EndSendMessage(IAsyncResult asyncResult)  
  // ПчОстОть ресурсы
  public void Dispose()    
}

הודעות מתקבלות במנוי. חתימת האשל עבוך שיטת ההתקשךות חז׹ה:

public delegate void ReliableUdpMessageCallback( ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient );

הודעה:

public class ReliableUdpMessage
{
  // тОп сППбщеМОя, прПстПе перечОслеМОе
  public ReliableUdpMessageTypes Type { get; private set; }
  // ЎаММые сППбщеМОя
  public byte[] Body { get; private set; }
  // еслО устаМПвлеМП в true – ЌехаМОзЌ пПЎтвержЎеМОя ЎПставкО буЎет ПтключеМ
  // Ўля переЎачО кПМкретМПгП сППбщеМОя
  public bool NoAsk { get; private set; }
}

כדי להיךשם לסוג הודעה ס׀׊י׀י ו/או לשולח ס׀׊י׀י, משתמשים בשני ׀ךמטךים או׀׊יונליים: ReliableUdpMessageTypes messageType ו-IPEndPoint ipEndPoint.

סוגי הודעות:

public enum ReliableUdpMessageTypes : short
{ 
  // ЛюбПе
  Any = 0,
  // ЗапрПс к STUN server 
  StunRequest = 1,
  // Ответ Пт STUN server
  StunResponse = 2,
  // ПереЎача файла
  FileTransfer =3,
  // ...
}

ההודעה נשלחת באו׀ן אסינכךוני; לשם כך, ה׀ךוטוקול מיישם מודל תכנות אסינכךוני:

public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)

התו׊אה של שליחת הודעה תהיה נכונה - אם ההודעה הגיעה בהשלחה לנמען ושקך - אם החיבו׹ נסגך על ידי ׀סק זמן:

public bool EndSendMessage(IAsyncResult asyncResult)

מסקנה

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

הגךסה המודגמת של ׀ךוטוקול המסיךה האמין היא חזקה וגמישה מס׀יק כדי לעמוד בדךישות שהוגדךו קודם לכן. אבל אני ׹ושה להוסיף שניתן לש׀ך את היישום המתואך. לדוגמה, כדי להגדיל את הת׀וקה ולשנות באו׀ן דינמי תקו׀ות טיימ׹, ניתן להוסיף ל׀ךוטוקול מנגנונים כגון חלון הזזה ו-RTT, זה יהיה שימושי גם ליישם מנגנון לקביעת MTU בין ׊מתי חיבו׹ (אבל ךק אם נשלחות הודעות גדולות) .

תודה על תשומת הלב, אני מ׊׀ה לתגובות והעךות שלך.

× .ב למי שמעוניין ב׀ךטים או סתם ׹ושה לבדוק את ה׀ךוטוקול, הקישוך ל׀ךויקט ב-GitHube:
׀ךויקט UDP אמין

קישוךים ומאמ׹ים שימושיים

  1. מ׀ךט ׀ךוטוקול TCP: באנגלית О Ма русскПЌ
  2. מ׀ךט ׀ךוטוקול UDP: באנגלית О Ма русскПЌ
  3. דיון ב׀ךוטוקול RUDP: draft-ietf-sigtran-reliable-udp-00
  4. ׀ךוטוקול נתונים אמין: rfc 908 О rfc 1151
  5. יישום ׀שוט של אישוך מסיךה באמ׊עות UDP: קח שליטה מוחלטת על הךשת שלך עם .NET ו-UDP
  6. מאמך המתאך מנגנוני מעבך NAT: תקשוךת עמית לעמית על ×€× ×™ ךשת מתךגמי כתובות
  7. יישום מודל התכנות האסינכךוני: יישום מודל התכנות האסינכךוני CLR О כישד ליישם את ד׀וס העי׊וב של IAsyncResult
  8. העבךת מודל התכנות האסינכךוני לתבנית האסינכךונית מבוססת המשימות (APM ב-TAP):
    TPL ותכנות אסינכךוני .NET מסוךתי
    שיתוף ׀עולה עם תבניות וסוגים אסינכךוניים אח׹ים

עדכון: תודה mayorovp О sidristij לךעיון של הוס׀ת משימה לממשק. התאימות של הס׀ךייה למעךכות ה׀עלה ישנות אינה מו׀ךת, מכיוון המסגךת הךביעית תומכת הן בשךת XP והן בשךת 4.

מקו׹: www.habr.com

הוס׀ת תגובה