Имплементација поузданог Удп протокола за .Нет

Интернет се одавно променио. Један од главних протокола Интернета – УДП се користи од стране апликација не само за испоруку датаграма и емитовања, већ и за обезбеђивање „пеер-то-пеер“ веза између мрежних чворова. Због свог једноставног дизајна, овај протокол има много раније непланираних употреба, међутим, недостаци протокола, као што је недостатак гарантоване испоруке, нису нигде нестали. Овај чланак описује имплементацију протокола загарантоване испоруке преко УДП-а.
Садржај:Улазак
Захтеви протокола
Поуздано УДП заглавље
Општи принципи протокола
Временска ограничења и тајмери ​​протокола
Поуздан дијаграм стања преноса УДП-а
Дубље у код. управљачка јединица преноса
Дубље у код. државе

Дубље у код. Стварање и успостављање веза
Дубље у код. Затварање везе по истеку времена
Дубље у код. Враћање преноса података
Поуздан УДП АПИ
Закључак
Корисни линкови и чланци

Улазак

Оригинална архитектура Интернета претпостављала је хомогени адресни простор у коме је сваки чвор имао глобалну и јединствену ИП адресу и могао је да комуницира директно са другим чворовима. Сада Интернет, у ствари, има другачију архитектуру - једну област ​​глобалних ИП адреса и многе области са приватним адресама скривеним иза НАТ уређаја.У овој архитектури, само уређаји у глобалном адресном простору могу лако да комуницирају са било ким на мрежи јер имају јединствену, глобално рутабилну ИП адресу. Чвор у приватној мрежи може да се повеже са другим чворовима на истој мрежи, а такође може да се повеже са другим добро познатим чворовима у глобалном адресном простору. Ова интеракција се постиже углавном захваљујући механизму превођења мрежних адреса. НАТ уређаји, као што су Ви-Фи рутери, креирају посебне уносе у табели превода за одлазне везе и мењају ИП адресе и бројеве портова у пакетима. Ово омогућава одлазне везе са приватне мреже на хостове у глобалном адресном простору. Али у исто време, НАТ уређаји обично блокирају сав долазни саобраћај осим ако нису постављена посебна правила за долазне везе.

Ова архитектура Интернета је довољно исправна за комуникацију клијент-сервер, где клијенти могу бити у приватним мрежама, а сервери имају глобалну адресу. Али то ствара потешкоће за директну везу два чвора између разноврсни приватне мреже. Директна веза између два чвора је важна за пеер-то-пеер апликације као што су пренос гласа (Скипе), добијање даљинског приступа рачунару (ТеамВиевер) или онлајн игре.

Једна од најефикаснијих метода за успостављање пеер-то-пеер везе између уређаја на различитим приватним мрежама назива се бушење рупа. Ова техника се најчешће користи са апликацијама заснованим на УДП протоколу.

Али ако ваша апликација захтева гарантовану испоруку података, на пример, преносите датотеке између рачунара, онда ће коришћење УДП-а имати много потешкоћа због чињенице да УДП није гарантовани протокол испоруке и не обезбеђује испоруку пакета по реду, за разлику од ТЦП-а. протокола.

У овом случају, да би се осигурала загарантована испорука пакета, потребно је имплементирати протокол слоја апликације који обезбеђује неопходну функционалност и ради преко УДП-а.

Одмах желим да напоменем да постоји техника пробијања ТЦП рупа за успостављање ТЦП веза између чворова у различитим приватним мрежама, али због недостатка подршке за то од стране многих НАТ уређаја, обично се не сматра главним начином повезивања такви чворови.

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

Захтеви протокола

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

Ови захтеви се у великој мери поклапају са захтевима Протокола поузданих података описаним у РФЦ 908 и РФЦ 1151, и ослањао сам се на те стандарде када сам развијао овај протокол.

Да бисмо разумели ове захтеве, погледајмо време преноса података између два мрежна чвора користећи ТЦП и УДП протоколе. Нека у оба случаја изгубимо један пакет.
Пренос неинтерактивних података преко ТЦП-а:Имплементација поузданог Удп протокола за .Нет

Као што видите из дијаграма, у случају губитка пакета, ТЦП ће открити изгубљени пакет и пријавити га пошиљаоцу тражећи број изгубљеног сегмента.
Пренос података преко УДП протокола:Имплементација поузданог Удп протокола за .Нет

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

Откривање грешке у ТЦП протоколу се постиже успостављањем везе са крајњим чвором, чувањем стања те везе, назначавањем броја бајтова послатих у заглављу сваког пакета и обавештавањем о пријему помоћу броја потврде.

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

За више информација о ТЦП протоколу, погледајте РФЦ 793, од УДП до РФЦ 768где су, у ствари, дефинисани.

Из наведеног је јасно да у циљу креирања поузданог протокола за испоруку порука преко УДП-а (у даљем тексту: Поуздан УДП), потребно је имплементирати механизме преноса података сличне ТЦП-у. Наиме:

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

Поред тога, потребно вам је:

  • сигнализира почетак поруке, да додели ресурсе за везу
  • сигнализира крај поруке, да проследи примљену поруку узводној апликацији и ослободи ресурсе протокола
  • дозволите протоколу специфичном за везу да онемогући механизам потврде испоруке да функционише као "чисти" УДП

Поуздано УДП заглавље

Подсетимо се да је УДП датаграм инкапсулиран у ИП датаграм. Поуздан УДП пакет је на одговарајући начин "умотан" у УДП датаграм.
Поуздана енкапсулација УДП заглавља:Имплементација поузданог Удп протокола за .Нет

Структура Поузданог УДП заглавља је прилично једноставна:

Имплементација поузданог Удп протокола за .Нет

  • Заставице - заставице за контролу пакета
  • МессагеТипе – тип поруке који користе упстреам апликације за претплату на одређене поруке
  • ТрансмиссионИд - број преноса, заједно са адресом и портом примаоца, јединствено идентификује везу
  • ПацкетНумбер - број пакета
  • Опције - додатне опције протокола. У случају првог пакета, користи се за означавање величине поруке

Заставе су следеће:

  • ФирстПацкет - први пакет поруке
  • НоАск – порука не захтева да се омогући механизам потврде
  • ЛастПацкет - последњи пакет поруке
  • РекуестФорПацкет - пакет за потврду или захтев за изгубљени пакет

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

Пошто је поуздан УДП фокусиран на гарантовани пренос порука између два чвора, мора бити у стању да успостави везу са другом страном. Да би успоставио везу, пошиљалац шаље пакет са заставицом ФирстПацкет, чији одговор ће значити да је веза успостављена. Сви пакети одговора, или, другим речима, пакети потврде, увек постављају вредност поља ПацкетНумбер на једну више од највеће вредности ПацкетНумбер успешно примљених пакета. Поље Опције за први послати пакет је величина поруке.

Сличан механизам се користи за прекид везе. ЛастПацкет заставица је постављена на последњем пакету поруке. У пакету одговора је назначен број последњег пакета + 1, што за страну која прима поруку значи успешну испоруку поруке.
Дијаграм успостављања и прекида везе:Имплементација поузданог Удп протокола за .Нет

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

Страна која прима пакете прима пакете. Сваки пакет се проверава да би се видело да ли спада у оквир за пренос. Пакети и дупликати који не падају у прозор се филтрирају. Јер Ако је величина прозора фиксна и иста за примаоца и пошиљаоца, онда у случају да се блок пакета испоручује без губитка, прозор се помера да прими пакете следећег блока података и добија се потврда испоруке. послао. Ако се прозор не попуни у року који је задао радни тајмер, тада ће се покренути провера који пакети нису испоручени и биће послати захтеви за поновну испоруку.
Дијаграм ретрансмисије:Имплементација поузданог Удп протокола за .Нет

Временска ограничења и тајмери ​​протокола

Постоји неколико разлога зашто се веза не може успоставити. На пример, ако је прималац ван мреже. У овом случају, када покушавате да успоставите везу, веза ће бити затворена по истеку времена. Имплементација поузданог УДП-а користи два тајмера за постављање временских ограничења. Први, радни тајмер, користи се за чекање одговора са удаљеног хоста. Ако се активира на страни пошиљаоца, последњи послати пакет се поново шаље. Ако тајмер истекне код примаоца, онда се врши провера изгубљених пакета и шаљу се захтеви за поновну испоруку.

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

Поуздан дијаграм стања преноса УДП-а

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

Имплементација поузданог Удп протокола за .Нет

Затворено - није заправо стање, то је почетна и крајња тачка за аутомат. За државу Затворено прима се контролни блок преноса који, имплементирајући асинхрони УДП сервер, прослеђује пакете одговарајућим везама и започиње обраду стања.

ФирстПацкетСендинг – почетно стање у којем се налази одлазна веза када се порука шаље.

У овом стању се шаље први пакет за нормалне поруке. За поруке без потврде слања, ово је једино стање у којем се шаље цела порука.

СендингЦицле – основно стање за пренос пакета порука.

Прелазак на њега из државе ФирстПацкетСендинг извршено након што је први пакет поруке послат. У том стању долазе сва признања и захтеви за ретрансмисије. Излазак из ње је могућ у два случаја - у случају успешне испоруке поруке или по истеку времена.

ФирстПацкетРецеивед – почетно стање за примаоца поруке.

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

За поруку која се састоји од једног пакета и послата без коришћења доказа о испоруци, ово је једино стање. Након обраде такве поруке, веза се затвара.

Склапање – основно стање за пријем пакета порука.

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

Завршен – затварање везе у случају успешног пријема целе поруке.

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

Дубље у код. управљачка јединица преноса

Један од кључних елемената поузданог УДП-а је блок контроле преноса. Задатак овог блока је да складишти тренутне везе и помоћне елементе, дистрибуира долазне пакете до одговарајућих веза, обезбеди интерфејс за слање пакета на везу и имплементира АПИ протокола. Контролни блок преноса прима пакете са УДП слоја и прослеђује их државној машини на обраду. За примање пакета, имплементира асинхрони УДП сервер.
Неки чланови класе РелиаблеУдпЦоннецтионЦонтролБлоцк:

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);
}

За сваки пренос поруке креира се структура која садржи информације о вези. Таква структура се зове запис везе.
Неки чланови класе РелиаблеУдпЦоннецтионРецорд:

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;
  //...
}

Дубље у код. државе

Државе имплементирају државну машину Релиабле УДП протокола, где се одвија главна обрада пакета. Апстрактна класа РелиаблеУдпСтате обезбеђује интерфејс за стање:

Имплементација поузданог Удп протокола за .Нет

Целокупну логику протокола имплементирају горе представљене класе, заједно са помоћном класом која обезбеђује статичке методе, као што је, на пример, конструисање заглавља РелиаблеУдп из записа везе.

Затим ћемо детаљно размотрити имплементацију метода интерфејса који одређују основне алгоритме протокола.

ДиспосеБиТимеоут Метход

Метода ДиспосеБиТимеоут је одговорна за ослобађање ресурса везе након истека времена и за сигнализацију успешне/неуспешне испоруке поруке.
РелиаблеУдпСтате.ДиспосеБиТимеоут:

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

То је само поништено у држави Завршен.
Цомплетед.ДиспосеБиТимеоут:

protected override void DisposeByTimeout(object record)
{
  ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
  // сообщаем об успешном получении сообщения
  SetAsCompleted(connectionRecord);        
}

ПроцессПацкетс метода

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

У стању Склапање метода је надјачана и одговорна је за проверу изгубљених пакета и прелазак у стање Завршен, у случају пријема последњег пакета и проласка успешне провере
Ассемблинг.ПроцессПацкетс:

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);
  }
}

У стању СендингЦицле овај метод се позива само на тајмеру и одговоран је за поновно слање последње поруке, као и за омогућавање тајмера за затварање везе.
СендингЦицле.ПроцессПацкетс:

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

У стању Завршен метода зауставља радни тајмер и шаље поруку претплатницима.
Цомплетед.ПроцессПацкетс:

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  if (connectionRecord.WaitForPacketsTimer != null)
    connectionRecord.WaitForPacketsTimer.Dispose();
  // собираем сообщение и передаем его подписчикам
  ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}

РецеивеПацкет метод

У стању ФирстПацкетРецеивед главни задатак методе је да утврди да ли је први пакет поруке заиста стигао на интерфејс, као и да прикупи поруку која се састоји од једног пакета.
ФирстПацкетРецеивед.РецеивеПацкет:

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));
}

У стању Склапање у методи РецеивеПацкет одвија се главни посао склапања поруке од долазних пакета.
Ассемблинг.РецеивеПацкет:

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);
  }
}

У стању Завршен једини задатак методе је да поново пошаље потврду о успешној испоруци поруке.
Цомплетед.РецеивеПацкет:

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);
}

У стању СендингЦицле у овој методи се шаље блок пакета.
СендингЦицле.СендПацкет:

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 );
  }
}

Дубље у код. Стварање и успостављање веза

Сада када смо видели основна стања и методе које се користе за руковање стањима, хајде да мало детаљније разложимо неколико примера како протокол функционише.
Дијаграм преноса података у нормалним условима:Имплементација поузданог Удп протокола за .Нет

Размотрите детаљно стварање запис везе за повезивање и слање првог пакета. Пренос увек иницира апликација која позива АПИ за слање порука. Затим се позива метод СтартТрансмиссион контролног блока преноса, који започиње пренос података за нову поруку.
Креирање одлазне везе:

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);
}

Након слања првог пакета, пошиљалац улази у стање СендингЦицле – сачекајте потврду испоруке пакета.
Страна која прима, користећи методу ЕндРецеиве, прима послани пакет, креира нови запис везе и прослеђује овај пакет, са унапред рашчлањеним заглављем, методу РецеивеПацкет стања на обраду ФирстПацкетРецеивед
Креирање везе на пријемној страни:

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);
}

Пријем првог пакета и слање потврде (ФирстПацкетРецеивед стање):

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);
  }
}

Дубље у код. Затварање везе по истеку времена

Руковање временским ограничењем је важан део поузданог УДП-а. Размотримо пример у којем је посредни чвор отказао и испорука података у оба смера постала је немогућа.
Дијаграм за затварање везе по временском ограничењу:Имплементација поузданог Удп протокола за .Нет

Као што се види из дијаграма, тајмер за рад пошиљаоца почиње одмах након слања блока пакета. Ово се дешава у методи СендПацкет стања СендингЦицле.
Омогућавање радног тајмера (стање СендингЦицле):

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

Периоди тајмера се постављају када се веза успостави. Подразумевани СхортТимерПериод је 5 секунди. У примеру, постављено је на 1,5 секунде.

За долазну везу, тајмер почиње након пријема последњег долазног пакета података, то се дешава у методи РецеивеПацкет стања Склапање
Омогућавање радног тајмера (стање склапања):

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);
  // ...
}

Ниједан више пакета није стигао на долазну везу док се чека радни тајмер. Тајмер се искључио и позвао методу ПроцессПацкетс, где су пронађени изгубљени пакети и први пут су послати захтеви за поновну испоруку.
Слање захтева за поновну испоруку (стање склапања):

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);
  }
}

Променљива ТимерСецондТри је подешена на прави. Ова варијабла је одговорна за поновно покретање радног тајмера.

На страни пошиљаоца, радни тајмер се такође покреће и последњи послати пакет се поново шаље.
Омогућавање тајмера затварања везе (стање СендингЦицле):

public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
  // ...        
  // отправляем повторно последний пакет 
  // ...        
  // включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
  StartCloseWaitTimer(connectionRecord);
}

Након тога, тајмер затварања везе почиње у одлазној вези.
РелиаблеУдпСтате.СтартЦлосеВаитТимер:

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

Након кратког времена, радни тајмер на страни примаоца поново се активира, захтеви се поново шаљу, након чега почиње тајмер затварања везе за долазну везу

Када се активирају тајмери ​​за затварање, сви ресурси оба записа везе се ослобађају. Пошиљалац пријављује неуспешну испоруку узводној апликацији (погледајте Поуздан УДП АПИ).
Отпуштање ресурса записа везе:

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);
      }
    }
    // ...
  }
}

Пошиљалац ће прихватити захтев за поновну испоруку и послати пакете који недостају. Вреди напоменути да је у овом тренутку пошиљалац већ покренуо тајмер затварања везе и, када се прими захтев, он се ресетује.
Поновно слање изгубљених пакета (стање СендингЦицле):

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);
  }
  // ...
}

Поуздан УДП АПИ

За интеракцију са протоколом за пренос података, постоји отворена класа Релиабле Удп, која је омотач преко контролног блока преноса. Ево најважнијих чланова класе:

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)

Закључак

Много тога није описано у овом чланку. Механизми упаривања нити, руковање изузетцима и грешкама, имплементација метода асинхроног слања порука. Али језгро протокола, опис логике за обраду пакета, успостављање везе и руковање временским ограничењима, требало би да вам буде јасно.

Демонстрирана верзија поузданог протокола испоруке је довољно робусна и флексибилна да испуни претходно дефинисане захтеве. Али желим да додам да се описана имплементација може побољшати. На пример, да би се повећала пропусност и динамички мењали периоди тајмера, механизми као што су клизни прозор и РТТ могу да се додају у протокол, такође ће бити корисно применити механизам за одређивање МТУ између чворова везе (али само ако се шаљу велике поруке) .

Хвала вам на пажњи, радујем се вашим коментарима и коментарима.

ПС За оне који су заинтересовани за детаље или само желе да тестирају протокол, линк ка пројекту на ГитХубе-у:
Поуздан УДП пројекат

Корисни линкови и чланци

  1. Спецификација ТЦП протокола: на енглеском језику и на руском
  2. Спецификација УДП протокола: на енглеском језику и на руском
  3. Дискусија о РУДП протоколу: драфт-иетф-сигтран-релиабле-удп-00
  4. Протокол поузданих података: РФЦ 908 и РФЦ 1151
  5. Једноставна имплементација потврде испоруке преко УДП-а: Преузмите потпуну контролу над својим умрежавањем уз .НЕТ и УДП
  6. Чланак који описује механизме преласка НАТ-а: Пеер-то-Пеер комуникација преко мрежних преводилаца адреса
  7. Имплементација модела асинхроног програмирања: Имплементација ЦЛР модела асинхроног програмирања и Како имплементирати ИАсинцРесулт образац дизајна
  8. Преношење модела асинхроног програмирања на асинхрони образац заснован на задацима (АПМ у ТАП):
    ТПЛ и традиционално .НЕТ асинхроно програмирање
    Интеракција са другим асинхроним обрасцима и типовима

Ажурирање: Хвала маиоровп и сидристиј за идеју додавања задатка у интерфејс. Компатибилност библиотеке са старим оперативним системима није нарушена, јер Четврти оквир подржава и КСП и 4 сервер.

Извор: ввв.хабр.цом

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