אָבער אויב דיין אַפּלאַקיישאַן ריקווייערז געראַנטיד עקספּרעס פון דאַטן, למשל, איר אַריבערפירן טעקעס צווישן קאָמפּיוטערס, די נוצן פון UDP וועט האָבן פילע שוועריקייטן רעכט צו דעם פאַקט אַז UDP איז נישט אַ געראַנטיד עקספּרעס פּראָטאָקאָל און טוט נישט צושטעלן פּאַקאַט עקספּרעס אין סדר, ניט ענלעך די TCP. פּראָטאָקאָל.
אין דעם פאַל, צו ענשור געראַנטיד פּאַקאַט עקספּרעס, עס איז פארלאנגט צו ינסטרומענט אַן אַפּלאַקיישאַן שיכטע פּראָטאָקאָל וואָס גיט די נייטיק פאַנגקשאַנאַליטי און אַרבעט איבער UDP.
איך ווילן צו טאָן גלייך אַז עס איז אַ טקפּ לאָך פּאַנטשינג טעכניק פֿאַר גרינדן טקפּ קאַנעקשאַנז צווישן נאָודז אין פאַרשידענע פּריוואַט נעטוואָרקס, אָבער ווייַל פון די פעלן פון שטיצן פֿאַר עס דורך פילע NAT דעוויסעס, עס איז יוזשאַוואַלי נישט גערעכנט ווי דער הויפּט וועג צו פאַרבינדן. אַזאַ נאָודז.
פֿאַר די רעשט פון דעם אַרטיקל, איך וועט פאָקוס בלויז אויף די ימפּלאַמענטיישאַן פון די געראַנטיד עקספּרעס פּראָטאָקאָל. די ימפּלאַמענטיישאַן פון די UDP לאָך פּאַנטשינג טעכניק וועט זיין דיסקרייבד אין די פאלגענדע אַרטיקלען.
די נויט פֿאַר עפעקטיוו אַריבערפירן פון גרויס דאַטן, י.ע. דער פּראָטאָקאָל מוזן ויסמיידן ומנייטיק פּאַקאַט רילייינג
עס זאָל זיין מעגלעך צו באָטל מאַכן די עקספּרעס באַשטעטיקונג מעקאַניזאַם (די פיייקייט צו פונקציאָנירן ווי אַ "ריין" ודפּ פּראָטאָקאָל)
פיייקייט צו ינסטרומענט באַפֿעלן מאָדע, מיט באַשטעטיקונג פון יעדער אָנזאָג
די גרונט אַפּאַראַט פון דאַטן אַריבערפירן איבער דעם פּראָטאָקאָל מוזן זיין אַ אָנזאָג
די רעקווירעמענץ לאַרגעלי צונויפפאַלן מיט די רעליאַבלע דאַטאַ פּראָטאָקאָל רעקווירעמענץ דיסקרייבד אין רפק 908 и רפק 1151, און איך רילייד אויף די סטאַנדאַרדס ווען דעוועלאָפּינג דעם פּראָטאָקאָל.
צו פֿאַרשטיין די רעקווירעמענץ, לאָמיר קוקן אין די טיימינג פון דאַטן אַריבערפירן צווישן צוויי נעץ נאָודז ניצן די TCP און UDP פּראָטאָקאָלס. זאל אין ביידע קאַסעס מיר האָבן איין פּאַקאַט פאַרפאַלן. אַריבערפירן פון ניט-ינטעראַקטיוו דאַטן איבער TCP:
ווי איר קענען זען פון די דיאַגראַמע, אין פאַל פון פּאַקאַט אָנווער, TCP וועט דעטעקט די פאַרפאַלן פּאַקאַט און באַריכט עס צו די סענדער דורך אַסקינג די נומער פון די פאַרפאַלן אָפּשניט. דאַטן אַריבערפירן דורך UDP פּראָטאָקאָל:
UDP טוט נישט נעמען קיין אָנווער דיטעקשאַן סטעפּס. קאָנטראָל פון טראַנסמיסיע ערראָרס אין די UDP פּראָטאָקאָל איז לעגאַמרע די פֿאַראַנטוואָרטלעכקייט פון די אַפּלאַקיישאַן.
טעות דיטעקשאַן אין די TCP פּראָטאָקאָל איז אַטשיווד דורך גרינדן אַ פֿאַרבינדונג מיט אַ סוף נאָדע, סטאָרינג די שטאַט פון די קשר, ינדאַקייטינג די נומער פון ביטעס געשיקט אין יעדער פּאַקאַט כעדער, און אָנזאָג ריסיץ ניצן אַ דערקענטעניש נומער.
דערצו, צו פֿאַרבעסערן פאָרשטעלונג (ד"ה שיקן מער ווי איין אָפּשניט אָן ריסיווינג אַ דערקענטעניש), די TCP פּראָטאָקאָל ניצט די אַזוי גערופענע טראַנסמיסיע פֿענצטער - די נומער פון ביטעס פון דאַטן וואָס דער סענדער פון די אָפּשניט יקספּעקץ צו באַקומען.
פֿאַר מער אינפֿאָרמאַציע וועגן די TCP פּראָטאָקאָל, זען רפק 793, פון ודפּ צו רפק 768ווו, אין פאַקט, זיי זענען דיפיינד.
פון די אויבן, עס איז קלאָר אַז אין סדר צו שאַפֿן אַ פאַרלאָזלעך אָנזאָג עקספּרעס פּראָטאָקאָל איבער UDP (דערנאָך ריפערד צו ווי פאַרלאָזלעך ודפּ), עס איז פארלאנגט צו ינסטרומענט דאַטן אַריבערפירן מעקאַניזאַמז ענלעך צו TCP. נעמליך:
ראַטעווען קשר שטאַט
נוצן סעגמענט נאַמבערינג
נוצן ספּעציעל באַשטעטיקונג פּאַקאַדזשאַז
ניצן אַ סימפּלאַפייד ווינדאָוינג מעקאַניזאַם צו פאַרגרעסערן פּראָטאָקאָל טרופּוט
אין דערצו, איר דאַרפֿן:
סיגנאַל די אָנהייב פון אַ אָנזאָג, צו אַלאַקייט רעסורסן פֿאַר די קשר
סיגנאַל דער סוף פון אַ אָנזאָג, צו פאָרן די באקומען אָנזאָג צו די אַפּסטרים אַפּלאַקיישאַן און מעלדונג פּראָטאָקאָל רעסורסן
לאָזן די קשר-ספּעציפיש פּראָטאָקאָל צו דיסייבאַל די עקספּרעס באַשטעטיקונג מעקאַניזאַם צו פונקציאָנירן ווי "ריין" UDP
פאַרלאָזלעך ודפּ כעדער
צוריקרופן אַז אַ UDP דאַטאַגראַם איז ענקאַפּסאַלייטיד אין אַן IP דאַטאַגראַם. די פאַרלאָזלעך ודפּ פּאַקאַט איז אַפּראָופּרייטלי "אלנגעוויקלט" אין אַ ודפּ דאַטאַגראַם. פאַרלאָזלעך ודפּ כעדער ענקאַפּסולאַטיאָן:
די סטרוקטור פון די פאַרלאָזלעך UDP כעדער איז גאַנץ פּשוט:
פלאַגס - פּעקל קאָנטראָל פלאַגס
MessageType - אָנזאָג טיפּ געניצט דורך אַפּסטרים אַפּלאַקיישאַנז צו אַבאָנירן צו ספּעציפיש אַרטיקלען
TransmissionId - די נומער פון די טראַנסמיסיע, צוזאַמען מיט די אַדרעס און פּאָרט פון די באַקומער, יידענאַפייד יוניקלי די קשר
פּאַקאַטנומער - פּאַקאַט נומער
אָפּציעס - נאָך פּראָטאָקאָל אָפּציעס. אין דעם פאַל פון דער ערשטער פּאַקאַט, עס איז געניצט צו אָנווייַזן די גרייס פון דעם אָנזאָג
זינט Reliable UDP איז פאָוקיסט אויף געראַנטיד אָנזאָג טראַנסמיסיע צווישן צוויי נאָודז, עס מוזן קענען צו פאַרלייגן אַ קשר מיט די אנדערע זייַט. צו פאַרלייגן אַ קשר, דער סענדער סענדז אַ פּאַקאַט מיט די FirstPacket פאָן, דער ענטפער צו וואָס וועט מיינען אַז די קשר איז געגרינדעט. אַלע ענטפער פּאַקיץ, אָדער, אין אנדערע ווערטער, דערקענטעניש פּאַקיץ, שטענדיק שטעלן די ווערט פון די פּאַקאַטנומבער פעלד צו איינער מער ווי די גרעסטע פּאַקקעטנומער ווערט פון הצלחה באקומען פּאַקיץ. די אָפּציעס פעלד פֿאַר דער ערשטער פּאַקאַט געשיקט איז די גרייס פון דעם אָנזאָג.
א ענלעך מעקאַניזאַם איז געניצט צו פאַרענדיקן אַ קשר. די לאַסטפּאַקקעט פאָן איז באַשטימט אויף די לעצטע פּאַקאַט פון די אָנזאָג. אין דער ענטפער פּאַקאַט, די נומער פון די לעצטע פּאַקאַט + 1 איז אנגעוויזן, וואָס פֿאַר די ריסיווינג זייַט מיטל אַ מצליח עקספּרעס פון די אָנזאָג. קשר פאַרלייגן און טערמאַניישאַן דיאַגראַמע:
ווען די קשר איז געגרינדעט, די דאַטן אַריבערפירן הייבט. דאַטן זענען טראַנסמיטטעד אין בלאַקס פון פּאַקיץ. יעדער בלאָק, אַחוץ די לעצטע, כּולל אַ פאַרפעסטיקט נומער פון פּאַקיץ. עס איז גלייַך צו די באַקומען / יבערשיקן פֿענצטער גרייס. די לעצטע בלאָק פון דאַטן קען האָבן ווייניקערע פּאַקיץ. נאָך שיקט יעדער בלאָק, די שיקט זייַט ווארטן פֿאַר אַ עקספּרעס באַשטעטיקונג אָדער אַ בקשה צו שייַעך-דיליווערינג פאַרפאַלן פּאַקיץ, געלאזן די באַקומען / יבערשיקן פֿענצטער עפענען צו באַקומען רעספּאָנסעס. נאָך ריסיווינג באַשטעטיקונג פון בלאָק עקספּרעס, די באַקומען / יבערשיקן פֿענצטער שיפץ און דער ווייַטער בלאָק פון דאַטן איז געשיקט.
די ריסיווינג זייַט נעמט די פּאַקיץ. יעדער פּאַקאַט איז אָפּגעשטעלט צו זען אויב עס פאלן אין די טראַנסמיסיע פֿענצטער. פּאַקאַץ און דופּליקאַטן וואָס טאָן ניט פאַלן אין די פֿענצטער זענען פילטערד. ווייַל אויב די גרייס פון דעם פֿענצטער איז פאַרפעסטיקט און די זעלבע פֿאַר די באַקומער און די סענדער, אין דעם פאַל פון אַ בלאָק פון פּאַקיץ איז איבערגעגעבן אָן אָנווער, די פֿענצטער איז שיפטיד צו באַקומען פּאַקיץ פון די ווייַטער בלאָק פון דאַטן און אַ עקספּרעס באַשטעטיקונג איז געשיקט. אויב די פֿענצטער איז נישט אָנגעפילט אין דער צייט באַשטימט דורך די אַרבעט טייַמער, אַ טשעק וועט זיין סטאַרטעד אויף וואָס פּאַקיץ זענען נישט איבערגעגעבן און ריקוועס פֿאַר ריליווערי וועט זיין געשיקט. רעטראַנסמיסיע דיאַגראַמע:
טיימאַוץ און פּראָטאָקאָל טיימערז
עס זענען עטלעכע סיבות וואָס אַ קשר קענען ניט זיין געגרינדעט. פֿאַר בייַשפּיל, אויב די ריסיווינג פּאַרטיי איז אָפפלינע. אין דעם פאַל, ווען איר פּרובירן צו פאַרלייגן אַ קשר, די קשר וועט זיין פארמאכט דורך טיימאַוט. די פאַרלאָזלעך ודפּ ימפּלאַמענטיישאַן ניצט צוויי טיימערז צו שטעלן טיימאַוץ. דער ערשטער, די אַרבעט טייַמער, איז געניצט צו וואַרטן פֿאַר אַ ענטפער פון די ווייַט באַלעבאָס. אויב עס איז פייערד אויף די סענדער זייַט, די לעצטע געשיקט פּאַקאַט איז פאַרצווייפלט. אויב די טייַמער יקספּייערז ביי די באַקומער, אַ טשעק פֿאַר פאַרפאַלן פּאַקיץ איז דורכגעקאָכט און ריקוועס פֿאַר ריליווערי זענען געשיקט.
די רגע טייַמער איז דארף צו פאַרמאַכן די קשר אין פאַל פון אַ פעלן פון קאָמוניקאַציע צווישן די נאָודז. פֿאַר די סענדער זייַט, עס סטאַרץ גלייך נאָך די אַרבעט טייַמער יקספּייערז און ווארטן פֿאַר אַ ענטפער פון די ווייַט נאָדע. אויב עס איז קיין ענטפער אין די ספּעסיפיעד צייט, די קשר איז טערמאַנייטיד און רעסורסן זענען באפרייט. פֿאַר די ריסיווינג זייַט, די נאָענט טייַמער איז סטאַרטעד נאָך די אַרבעט טייַמער יקספּייערז צוויי מאָל. דאָס איז נייטיק צו פאַרזיכערן קעגן די אָנווער פון די באַשטעטיקונג פּאַקאַט. ווען די טייַמער יקספּייערז, די קשר איז אויך טערמאַנייטיד און רעסורסן זענען באפרייט.
פאַרלאָזלעך ודפּ טראַנסמיסיע שטאַט דיאַגראַמע
די פּרינסאַפּאַלז פון דעם פּראָטאָקאָל זענען ימפּלאַמענאַד אין אַ ענדלעך שטאַט מאַשין, יעדער שטאַט פון וואָס איז פאַראַנטוואָרטלעך פֿאַר אַ זיכער לאָגיק פון פּאַקאַט פּראַסעסינג.
פאַרלאָזלעך ודפּ שטאַט דיאַגראַמע:
SendingCycle - ערד שטאַט פֿאַר די טראַנסמיסיע פון אָנזאָג פּאַקיץ.
יבערגאַנג צו עס פון די שטאַט FirstPacketSending געפירט אויס נאָך דער ערשטער פּעקל פון דעם אָנזאָג איז געשיקט. עס איז אין דעם שטאַט אַז אַלע דערקענונג און ריקוועס פֿאַר ריטראַנסמישאַנז קומען. אַרויסגאַנג פון עס איז מעגלעך אין צוויי קאַסעס - אין פאַל פון מצליח עקספּרעס פון דעם אָנזאָג אָדער דורך טיימאַוט.
FirstPacket Received - דער ערשט שטאַט פֿאַר די באַקומער פון דעם אָנזאָג.
עס טשעקס די קערעקטנאַס פון די אָנהייב פון די טראַנסמיסיע, קריייץ די נייטיק סטראַקטשערז, און סענדז אַ דערקענטעניש פון קאַבאָלע פון דער ערשטער פּאַקאַט.
פֿאַר אַ אָנזאָג וואָס באשטייט פון אַ איין פּאַקאַט און איז געשיקט אָן ניצן דערווייַז פון עקספּרעס, דאָס איז דער בלויז שטאַט. נאָך פּראַסעסינג אַזאַ אַ אָנזאָג, די קשר איז פארמאכט.
עס שרייבט פּאַקיץ צו צייַטווייַליק סטאָרידזש, טשעקס פֿאַר פּאַקאַט אָנווער, סענדז אַקנאַלידזשמאַנץ פֿאַר די עקספּרעס פון אַ בלאָק פון פּאַקיץ און די גאנצע אָנזאָג, און סענדז ריקוועס פֿאַר ריליווערי פון פאַרפאַלן פּאַקיץ. אין פאַל פון מצליח קאַבאָלע פון די גאנצע אָנזאָג, די קשר גייט אין די שטאַט געענדיקט, אַנדערש, אַ טיימאַוט יקסידז.
געענדיקט - קלאָוזינג די קשר אין פאַל פון מצליח קאַבאָלע פון די גאנצע אָנזאָג.
דעם שטאַט איז נייטיק פֿאַר די פֿאַרזאַמלונג פון די אָנזאָג און פֿאַר די פאַל ווען די עקספּרעס באַשטעטיקונג פון די אָנזאָג איז פאַרפאַלן אויף די וועג צו די סענדער. דעם שטאַט איז יקסייטאַד דורך אַ טיימאַוט, אָבער די קשר איז גערעכנט ווי הצלחה פֿאַרמאַכט.
דיפּער אין די קאָד. טראַנסמיסיע קאָנטראָל אַפּאַראַט
איינער פון די שליסל עלעמענטן פון Reliable 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;
//...
}
ימפּלאַמענטיישאַן פון ייסינגקראַנאַס ודפּ סערווער:
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;
//...
}
דיפּער אין די קאָד. שטאַטן
שטאַטן ינסטרומענט די שטאַט מאַשין פון די פאַרלאָזלעך ודפּ פּראָטאָקאָל, ווו די הויפּט פּראַסעסינג פון פּאַקיץ נעמט אָרט. די אַבסטראַקט קלאַס ReliableUdpState גיט אַן צובינד פֿאַר די שטאַט:
די גאנצע לאָגיק פון דעם פּראָטאָקאָל איז ימפּלאַמענאַד דורך די קלאסן דערלאנגט אויבן, צוזאַמען מיט אַ אַגזיליערי קלאַס וואָס גיט סטאַטיק מעטהאָדס, אַזאַ ווי, למשל, קאַנסטראַקטינג די ReliableUdp כעדער פֿון די קשר רעקאָרד.
ווייַטער, מיר וועלן באַטראַכטן אין דעטאַל די ימפּלאַמענטיישאַן פון די צובינד מעטהאָדס וואָס באַשטימען די יקערדיק אַלגערידאַמז פון דעם פּראָטאָקאָל.
DisposeByTimeout אופֿן
די DisposeByTimeout אופֿן איז פאַראַנטוואָרטלעך פֿאַר ריליסינג קשר רעסורסן נאָך אַ טיימאַוט און פֿאַר סיגנאַלינג אַ מצליח / ניט געראָטן אָנזאָג עקספּרעס. ReliableUdpState.DisposeByTimeout:
קענען Assembling דער אופֿן איז אָווועררידאַן און איז פאַראַנטוואָרטלעך פֿאַר קאָנטראָלירונג פֿאַר פאַרפאַלן פּאַקיץ און יבערגאַנג צו די שטאַט געענדיקט, אין פאַל פון ריסיווינג די לעצטע פּאַקאַט און דורכגעגאנגען אַ מצליח טשעק אַסעמבלינג. פּראָצעס פּאַקיץ:
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 אופֿן
קענען FirstPacket Received די הויפּט אַרבעט פון דעם אופֿן איז צו באַשליסן צי דער ערשטער אָנזאָג פּאַקאַט אַקשלי אנגעקומען צו די צובינד, און אויך צו זאַמלען אַ אָנזאָג קאַנסיסטינג פון אַ איין פּאַקאַט. 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);
}
}
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));
}
קענען 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);
}
}
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 );
}
}
דיפּער אין די קאָד. שאפן און פאַרלייגן קאַנעקשאַנז
איצט אַז מיר האָבן געזען די יקערדיק שטאַטן און די מעטהאָדס געניצט צו שעפּן שטאַטן, לאָזן אונדז ברעכן אַראָפּ עטלעכע ביישפילן פון ווי דער פּראָטאָקאָל אַרבעט אין אַ ביסל מער דעטאַל. דאַטאַ טראַנסמיסיע דיאַגראַמע אונטער נאָרמאַל טנאָים:
באַטראַכטן אין דעטאַל די שאַפונג קשר רעקאָרד צו פאַרבינדן און שיקן די ערשטער פּאַקאַט. די אַריבערפירן איז שטענדיק ינישיייטיד דורך די אַפּלאַקיישאַן אַז רופט די שיקן אָנזאָג אַפּי. דערנאָך, די 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]);
}
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 אופֿן פון די שטאַט פֿאַר פּראַסעסינג FirstPacket Received שאַפֿן אַ קשר אויף די ריסיווינג זייַט:
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. באַטראַכטן אַ בייַשפּיל אין וואָס אַ ינטערמידייט נאָדע ניט אַנדערש און דאַטן עקספּרעס אין ביידע אינסטרוקציעס געווארן אוממעגלעך. דיאַגראַמע פֿאַר קלאָוזינג אַ קשר דורך טיימאַוט:
ווי קענען זיין געזען פון די דיאַגראַמע, די סענדער ס אַרבעט טייַמער סטאַרץ מיד נאָך שיקן אַ בלאָק פון פּאַקיץ. דאָס כאַפּאַנז אין די 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 אופֿן פון די שטאַט Assembling ענייבאַלינג די אַרבעט טייַמער (אַסעמבלינג שטאַט):
ניט מער פּאַקיץ אנגעקומען אויף די ינקאַמינג קשר בשעת ווארטן פֿאַר די אַרבעט טייַמער. דער טייַמער איז אַוועק און גערופן די 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);
}
}
דיפּער אין די קאָד. ריסטאָרינג דאַטן אַריבערפירן
דאַטן טראַנסמיסיע אָפּזוך דיאַגראַמע אין פאַל פון פּאַקאַט אָנווער:
ווי שוין דיסקאַסט אין קלאָוזינג די קשר אויף טיימאַוט, ווען די אַרבעט טייַמער יקספּייערז, דער ופנעמער וועט קאָנטראָלירן פֿאַר פאַרפאַלן פּאַקיץ. אין פאַל פון פּאַקאַט אָנווער, אַ רשימה פון די נומער פון פּאַקיץ וואָס האט נישט דערגרייכן דעם באַקומער וועט זיין צונויפגעשטעלט. די נומערן זענען אריין אין די לאָסטפּאַקקעץ מענגע פון אַ ספּעציפיש קשר, און ריקוועס פֿאַר ריליווערי זענען געשיקט. שיקט ריקוועס צו ריליווער פּאַקאַדזשאַז (אַסעמבלינג שטאַט):
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);
}
// ...
}
פאַרלאָזלעך UDP API
צו ינטעראַקט מיט די דאַטן אַריבערפירן פּראָטאָקאָל, עס איז אַן אָפֿן רעליאַבלע ודפּ קלאַס, וואָס איז אַ ראַפּער איבער די אַריבערפירן קאָנטראָל בלאָק. דאָ זענען די מערסט וויכטיק מיטגלידער פון דער קלאַס:
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; }
}
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: פאַרלאָזלעך ודפּ פּראָיעקט
דערהייַנטיקן: דאַנקען דיר מייַאָראָוופּ и sidristij פֿאַר דעם געדאַנק פון אַדינג אַ אַרבעט צו די צובינד. די קאַמפּאַטאַבילאַטי פון דער ביבליאָטעק מיט אַלט אָפּערייטינג סיסטעמען איז נישט ווייאַלייטיד, ווייַל די 4 פריימווערק שטיצט ביידע XP און 2003 סערווער.