Имплементација на протоколот Reliable Udp за .Net

Интернетот е променет одамна. Еден од главните протоколи на Интернет - UDP се користи од апликациите не само за испорака на датаграми и емитувања, туку и за обезбедување "peer-to-peer" врски помеѓу мрежните јазли. Поради едноставниот дизајн, овој протокол има многу претходно непланирани употреби, меѓутоа, недостатоците на протоколот, како што е недостатокот на гарантирана испорака, никаде не исчезнаа. Оваа статија ја опишува имплементацијата на протоколот за гарантирана испорака преку UDP.
Содржина:Влегување
Протоколски барања
Сигурен UDP заглавие
Општи принципи на протоколот
Истекнувања и протоколарни тајмери
Сигурен дијаграм на состојба на пренос на UDP
Подлабоко во кодот. контролна единица за пренос
Подлабоко во кодот. државите

Подлабоко во кодот. Креирање и воспоставување врски
Подлабоко во кодот. Затворање на врската при истекот на времето
Подлабоко во кодот. Враќање на пренос на податоци
Сигурен UDP API
Заклучок
Корисни врски и статии

Влегување

Оригиналната архитектура на Интернет претпоставуваше хомоген адресен простор во кој секој јазол има глобална и единствена IP адреса и може директно да комуницира со други јазли. Сега Интернетот, всушност, има поинаква архитектура - една област на глобални IP адреси и многу области со приватни адреси скриени зад уредите NAT.Во оваа архитектура, само уредите во глобалниот адресен простор можат лесно да комуницираат со кој било на мрежата бидејќи тие имаат единствена, глобално рутирачка IP адреса. Јазол на приватна мрежа може да се поврзе со други јазли на истата мрежа, а исто така може да се поврзе и со други добро познати јазли во глобалниот адресен простор. Оваа интеракција се постигнува во голема мера поради механизмот за преведување на мрежните адреси. NAT уредите, како што се рутерите за Wi-Fi, создаваат специјални записи од табелата за превод за појдовни врски и ги менуваат IP адресите и броевите на портите во пакетите. Ова овозможува појдовни врски од приватната мрежа до хостовите во глобалниот адресен простор. Но, во исто време, NAT уредите обично го блокираат целиот дојдовен сообраќај, освен ако не се постават посебни правила за дојдовни врски.

Оваа архитектура на Интернет е доволно коректна за комуникација клиент-сервер, каде што клиентите можат да бидат во приватни мрежи, а серверите имаат глобална адреса. Но, тоа создава тешкотии за директно поврзување на два јазли помеѓу различни приватни мрежи. Директната врска помеѓу два јазли е важна за peer-to-peer апликации како што се пренос на глас (Skype), стекнување далечински пристап до компјутер (TeamViewer) или онлајн игри.

Еден од најефикасните методи за воспоставување на peer-to-peer конекција помеѓу уредите на различни приватни мрежи се нарекува дупчење со дупки. Оваа техника најчесто се користи со апликации базирани на протоколот UDP.

Но, ако вашата апликација бара загарантирана испорака на податоци, на пример, пренесувате датотеки помеѓу компјутери, тогаш користењето на UDP ќе има многу потешкотии поради фактот што UDP не е гарантиран протокол за испорака и не обезбедува испорака на пакети по ред, за разлика од TCP протокол.

Во овој случај, за да се обезбеди загарантирана испорака на пакети, потребно е да се имплементира протокол за апликациски слој кој ја обезбедува потребната функционалност и работи преку UDP.

Сакам веднаш да напоменам дека постои техника на пробивање на TCP дупки за воспоставување на TCP врски помеѓу јазли во различни приватни мрежи, но поради недостатокот на поддршка за тоа од многу NAT уреди, обично не се смета за главен начин за поврзување такви јазли.

Во остатокот од овој напис, ќе се фокусирам само на имплементацијата на протоколот за гарантирана испорака. Имплементацијата на техниката на дупчење на дупки UDP ќе биде опишана во следните написи.

Протоколски барања

  1. Сигурна испорака на пакети имплементирана преку механизам за позитивна повратна информација (т.н. позитивно признание)
  2. Потребата за ефикасен пренос на големи податоци, т.е. протоколот мора да избегне непотребно пренесување на пакети
  3. Треба да биде можно да се откаже механизмот за потврда за испорака (способност да функционира како „чист“ протокол UDP)
  4. Способност да се имплементира команден режим, со потврда на секоја порака
  5. Основната единица за пренос на податоци преку протоколот мора да биде порака

Овие барања во голема мера се совпаѓаат со барањата на протоколот за веродостојни податоци опишани во РФЦ 908 година и РФЦ 1151 година, и јас се потпирав на тие стандарди при развивањето на овој протокол.

За да ги разбереме овие барања, да го погледнеме времето на пренос на податоци помеѓу два мрежни јазли користејќи ги протоколите TCP и UDP. Нека во двата случаи ќе имаме изгубен еден пакет.
Пренос на неинтерактивни податоци преку TCP:Имплементација на протоколот Reliable Udp за .Net

Како што можете да видите од дијаграмот, во случај на загуба на пакети, TCP ќе го открие изгубениот пакет и ќе го пријави до испраќачот барајќи го бројот на изгубениот сегмент.
Пренос на податоци преку UDP протокол:Имплементација на протоколот Reliable Udp за .Net

UDP не презема никакви чекори за откривање загуби. Контролата на грешките во преносот во протоколот UDP е целосна одговорност на апликацијата.

Откривањето на грешка во протоколот TCP се постигнува со воспоставување врска со краен јазол, складирање на состојбата на таа врска, означување на бројот на бајти испратени во секое заглавие на пакетот и известување за сметки со помош на број за потврда.

Дополнително, за да се подобрат перформансите (т.е. да се испратат повеќе од еден сегмент без да се добие потврда), протоколот TCP го користи таканаречениот прозорец за пренос - бројот на бајти на податоци што испраќачот на сегментот очекува да ги прими.

За повеќе информации за протоколот TCP, видете РФЦ 793 година, од UDP до РФЦ 768 годинакаде што всушност се дефинирани.

Од горенаведеното, јасно е дека со цел да се создаде сигурен протокол за испорака на пораки преку UDP (во натамошниот текст: Сигурен UDP), потребно е да се имплементираат механизми за пренос на податоци слични на TCP. Имено:

  • зачувај ја состојбата на врската
  • користете нумерирање на сегменти
  • користете специјални пакети за потврда
  • користете поедноставен механизам за прозорци за да го зголемите протокот на протоколот

Дополнително, ви треба:

  • сигнал за почеток на порака, за да се распределат ресурси за врската
  • сигнализирајте го крајот на пораката, за да ја пренесете добиената порака до апликацијата upstream и да ги ослободите ресурсите на протоколот
  • дозволете протоколот специфичен за врската да го оневозможи механизмот за потврда за испорака да функционира како „чист“ UDP

Сигурен UDP заглавие

Потсетете се дека UDP датаграмот е инкапсулиран во IP датаграм. Reliable UDP пакетот е соодветно „завиткан“ во UDP датаграм.
Сигурна енкапсулација на заглавието на UDP:Имплементација на протоколот Reliable Udp за .Net

Структурата на заглавието Reliable UDP е прилично едноставна:

Имплементација на протоколот Reliable Udp за .Net

  • Знамиња - знаменца за контрола на пакети
  • MessageType - тип на порака што го користат апликациите нагоре за да се претплатите на одредени пораки
  • TransmissionId - бројот на преносот, заедно со адресата и пристаништето на примачот, единствено ја идентификува врската
  • PacketNumber - број на пакет
  • Опции - дополнителни опции за протокол. Во случајот на првиот пакет, тој се користи за означување на големината на пораката

Знамињата се како што следува:

  • FirstPacket - првиот пакет од пораката
  • NoAsk - пораката не бара да се овозможи механизам за потврда
  • LastPacket - последниот пакет од пораката
  • RequestForPacket - пакет за потврда или барање за изгубен пакет

Општи принципи на протоколот

Бидејќи доверливиот UDP е фокусиран на гарантиран пренос на пораки помеѓу два јазли, мора да може да воспостави врска со другата страна. За да воспостави врска, испраќачот испраќа пакет со знаменцето FirstPacket, чиј одговор ќе значи дека врската е воспоставена. Сите пакети за одговор, или, со други зборови, пакети за потврда, секогаш ја поставуваат вредноста на полето PacketNumber за еден повеќе од најголемата вредност PacketNumber на успешно примените пакети. Полето Опции за првиот испратен пакет е големината на пораката.

Сличен механизам се користи за прекинување на врската. Знамето LastPacket е поставено на последниот пакет од пораката. Во пакетот одговор е означен бројот на последниот пакет + 1, што за страната примач значи успешно доставување на пораката.
Дијаграм за воспоставување и завршување на врската:Имплементација на протоколот Reliable Udp за .Net

Кога ќе се воспостави врската, започнува преносот на податоци. Податоците се пренесуваат во блокови од пакети. Секој блок, освен последниот, содржи фиксен број на пакети. Тоа е еднакво на големината на прозорецот за примање/пренос. Последниот блок на податоци може да има помалку пакети. По испраќањето на секој блок, страната што испраќа чека потврда за испорака или барање за повторно доставување на изгубени пакети, оставајќи го отворен прозорец за примање/пренос за да прима одговори. По добивањето на потврда за испорака на блок, прозорецот за примање/пренос се поместува и се испраќа следниот блок на податоци.

Приемната страна ги прима пакетите. Секој пакет се проверува за да се види дали спаѓа во прозорецот за пренос. Пакетите и дупликатите што не спаѓаат во прозорецот се филтрираат. Бидејќи Ако големината на прозорецот е фиксна и иста за примачот и испраќачот, тогаш во случај кога блок пакети се испорачуваат без загуба, прозорецот се префрла да прима пакети од следниот блок податоци и се потврдува потврдата за испорака. испратени. Ако прозорецот не се пополни во рокот поставен од работниот тајмер, тогаш ќе се започне проверка на која пакети не се испорачани и ќе се испраќаат барања за повторно доставување.
Дијаграм за реемитување:Имплементација на протоколот Reliable Udp за .Net

Истекнувања и протоколарни тајмери

Постојат неколку причини зошто не може да се воспостави врска. На пример, ако страната што прима е офлајн. Во овој случај, кога се обидувате да воспоставите врска, врската ќе биде затворена со истек на време. Имплементацијата на Reliable UDP користи два тајмери ​​за да постави тајм-аут. Првиот, работниот тајмер, се користи за да се чека одговор од оддалечениот домаќин. Ако пука на страната на испраќачот, тогаш последниот испратен пакет повторно се испраќа. Ако тајмерот истече кај примачот, тогаш се врши проверка на изгубени пакети и се испраќаат барања за повторно доставување.

Вториот тајмер е потребен за затворање на врската во случај на недостаток на комуникација помеѓу јазлите. За страната на испраќачот, тој започнува веднаш по истекот на работниот тајмер и чека одговор од оддалечениот јазол. Доколку нема одговор во наведениот период, врската се прекинува и ресурсите се ослободуваат. За приемната страна, тајмерот за затворање на врската се стартува откако работниот тајмер ќе истече двапати. Ова е неопходно за да се осигураме од губење на пакетот за потврда. Кога ќе истече тајмерот, врската исто така се прекинува и ресурсите се ослободуваат.

Сигурен дијаграм на состојба на пренос на UDP

Принципите на протоколот се имплементирани во машина со конечни состојби, чијашто состојба е одговорна за одредена логика на обработка на пакети.
Сигурен дијаграм на состојбата на UDP:

Имплементација на протоколот Reliable Udp за .Net

Затворено - не е навистина состојба, тоа е почетна и крајна точка за автоматот. За државата Затворено се прима контролен блок за пренос, кој, имплементирајќи асинхрон UDP сервер, ги препраќа пакетите до соодветните врски и започнува со обработка на состојбата.

FirstPacketSending – почетната состојба во која се наоѓа појдовната врска кога пораката е испратена.

Во оваа состојба се испраќа првиот пакет за нормални пораки. За пораки без потврда за испраќање, ова е единствената состојба во која се испраќа целата порака.

Циклус на испраќање – основна состојба за пренос на пакети со пораки.

Транзиција кон неа од државата 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 од записот за поврзување.

Следно, детално ќе ја разгледаме имплементацијата на методите на интерфејсот кои ги одредуваат основните алгоритми на протоколот.

Метод на 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.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.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.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);
}

По испраќањето на првиот пакет, испраќачот влегува во состојбата Циклус на испраќање – почекајте потврда за испорака на пакетот.
Приемната страна, користејќи го методот 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);
  }
}

Подлабоко во кодот. Затворање на врската при истекот на времето

Ракувањето со истекот на времето е важен дел од доверливиот UDP. Размислете за пример во кој пропадна среден јазол и испораката на податоци во двете насоки стана невозможна.
Дијаграм за затворање врска со истек на време:Имплементација на протоколот Reliable Udp за .Net

Како што може да се види од дијаграмот, работниот тајмер на испраќачот започнува веднаш по испраќањето на блок пакети. Ова се случува во методот SendPacket на државата Циклус на испраќање.
Овозможување на работниот тајмер (состојба на 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 е поставена на вистина. Оваа променлива е одговорна за рестартирање на работниот тајмер.

Од страната на испраќачот, работниот тајмер исто така се активира и последниот испратен пакет повторно се испраќа.
Овозможување тајмер за затворање на врската (состојба на циклус на испраќање):

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 секунди.

По кратко време, работниот тајмер од страната на примачот повторно се вклучува, барањата се испраќаат повторно, по што започнува тајмерот за затворање на врската за дојдовната врска

Кога ќе се активираат тајмерите за затворање, сите ресурси на двата записи за поврзување се ослободуваат. Испраќачот го пријавува неуспехот на испорака до апликацијата upstream (видете 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);
  }
  // ...
}

Сигурен UDP API

За да комуницирате со протоколот за пренос на податоци, постои отворена класа 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. Сигурен протокол за податоци: РФЦ 908 година и РФЦ 1151 година
  5. Едноставна имплементација на потврда за испорака преку UDP: Преземете целосна контрола врз вашето вмрежување со .NET и UDP
  6. Напис што ги опишува механизмите за преминување на NAT: Peer-to-peer комуникација преку преведувачи на адреси на мрежата
  7. Имплементација на моделот на асинхроно програмирање: Имплементирање на моделот за асинхроно програмирање CLR и Како да се имплементира шемата за дизајнирање IAsyncResult
  8. Пренесување на моделот за асинхрони програмирање на асинхроната шема заснована на задачи (APM во TAP):
    TPL и традиционално асинхроно програмирање .NET
    Интеропирајте со други асинхрони обрасци и типови

Ажурирање: Ви благодариме градоначалникот и сидристиј за идејата за додавање задача на интерфејсот. Компатибилноста на библиотеката со старите оперативни системи не е нарушена, бидејќи Четвртата рамка поддржува и XP и 4 сервер.

Извор: www.habr.com

Додадете коментар