Pêkanîna protokola Udp ya pêbawer ji bo .Net

Înternetê demek dirêj berê guhertiye. Yek ji protokolên sereke yên Înternetê - UDP ji hêla serîlêdanan ve ne tenê ji bo radestkirina datagram û weşanan, lê di heman demê de ji bo peydakirina girêdanên "peer-to-peer" di navbera girêkên torê de tê bikar anîn. Ji ber sêwirana wê ya hêsan, vê protokolê gelek karanîna berê yên neplankirî hene, lêbelê, kêmasiyên protokolê, wekî nebûna radestkirina garantîkirî, li her deverê winda nebûne. Ev gotar pêkanîna protokola radestkirina garantîkirî ya li ser UDP vedibêje.
Contains:entry
Pêdiviyên Protokolê
Sernivîsa UDP ya pêbawer
Prensîbên giştî yên protokolê
Dem û demjimêrên protokolê
Diagrama dewleta veguheztina UDP ya pêbawer
Di kodê de kûrtir. yekîneya kontrola veguhestinê
Di kodê de kûrtir. dewletên

Di kodê de kûrtir. Çêkirin û Damezrandina Têkiliyan
Di kodê de kûrtir. Girtina pêwendiyê li ser wextê
Di kodê de kûrtir. Veguheztina daneyan vedigere
Pêbawer UDP API
encamê
Girêdan û gotarên kêrhatî

entry

Mîmariya orîjînal a Înternetê cîhek navnîşanek homojen dihesibîne ku tê de her nodek navnîşanek IP-ya gerdûnî û bêhempa ye û dikare rasterast bi girêkên din re têkilî daynin. Naha Înternet, bi rastî, xwedan mîmariyek cûda ye - yek deverek navnîşanên IP-ya gerdûnî û gelek deverên bi navnîşanên taybet ên li pişt cîhazên NAT-ê veşartî ne.Di vê mîmariyê de, tenê cîhazên di cîhê navnîşana gerdûnî de dikarin bi hêsanî bi her kesê li ser torê re têkilî daynin ji ber ku wan navnîşek IP-ya yekta, gerdûnî ya rêvekirî heye. Nodek li ser torgilokek taybet dikare bi girêkên din ên li ser heman torê ve girêbide, û hem jî dikare bi girêkên din ên naskirî yên di cîhê navnîşana gerdûnî de were girêdan. Ev têkilî bi piranî ji ber mekanîzmaya wergerandina navnîşana torê pêk tê. Amûrên NAT, wekî rêgezên Wi-Fi, ji bo girêdanên derketinê navnîşên tabloya wergerê yên taybetî diafirînin û navnîşanên IP-yê û hejmarên portê di pakêtan de diguhezînin. Ev rê dide girêdanên derketinê yên ji tora taybet bi mêvandarên li cîhê navnîşana gerdûnî. Lê di heman demê de, cîhazên NAT bi gelemperî hemî seyrûsefera hatinê asteng dikin heya ku qaîdeyên cûda ji bo girêdanên hatinî neyên danîn.

Ev mîmariya Înternetê ji bo pêwendiya xerîdar-server têra xwe rast e, ku xerîdar dikarin di torên taybet de bin, û server xwedan navnîşek gerdûnî ne. Lê ji bo girêdana rasterast a du girêkan di navbera wan de zehmetiyan çêdike newekhev torên taybet. Têkiliyek rasterast di navbera du girêkan de ji bo serîlêdanên peer-to-peer ên wekî veguheztina deng (Skype), bidestxistina gihandina dûr a komputerek (TeamViewer), an lîstika serhêl girîng e.

Yek ji rêgezên herî bi bandor ji bo sazkirina pêwendiyek peer-to-peer di navbera cîhazên li ser torên cûda yên taybet de jê re qulikê tê gotin. Ev teknîkî bi gelemperî bi serîlêdanên li ser bingeha protokola UDP-ê tê bikar anîn.

Lê heke serîlêdana we radestkirina daneya garantîkirî hewce dike, mînakî, hûn pelan di navbera komputeran de vediguhezînin, wê hingê karanîna UDP dê gelek dijwar be ji ber vê yekê ku UDP ne protokolek radestkirina garantî ye û berevajî TCP-ê bi rêzê radestkirina pakêtê peyda nake. protokol.

Di vê rewşê de, ji bo misogerkirina radestkirina pakêtê ya garantîkirî, pêdivî ye ku meriv protokolek qata serîlêdanê bicîh bike ku fonksiyona pêwîst peyda dike û li ser UDP-yê dixebite.

Ez dixwazim tavilê destnîşan bikim ku ji bo sazkirina girêdanên TCP-ê di navbera girêkên di torên cûda yên taybet de teknîkek qulkirina TCP-ê heye, lê ji ber nebûna piştgirîya wê ji hêla gelek amûrên NAT ve, ew bi gelemperî wekî riya sereke ya girêdanê nayê hesibandin. girêkên weha.

Ji bo mayî ya vê gotarê, ez ê tenê li ser pêkanîna protokola radestkirina garantî bisekinim. Pêkanîna teknîka qulikê ya UDP dê di gotarên jêrîn de were vegotin.

Pêdiviyên Protokolê

  1. Radestkirina pakêtê ya pêbawer ku bi mekanîzmayek vegerandina erênî ve hatî bicîh kirin (ku jê re pejirandina erênî tê gotin)
  2. Pêdiviya veguhestina bi bandor a daneyên mezin, ango. divê protokol ji veguheztina pakêtê ya nehewce dûr bikeve
  3. Pêdivî ye ku meriv mekanîzmaya pejirandina radestkirinê betal bike (qabiliyeta ku wekî protokola UDP ya "paqij" kar bike)
  4. Kapasîteya pêkanîna moda fermanê, bi pejirandina her peyamê
  5. Yekîneya bingehîn a veguhastina daneyê li ser protokolê divê peyamek be

Van hewcedariyên bi giranî bi daxwazên Protokola Daneyên pêbawer ên ku di nav de têne destnîşan kirin re hevaheng in rfc 908 и rfc 1151, û dema ku vê protokolê pêş dixist min pişta xwe da wan standardan.

Ji bo fêmkirina van hewcedariyên, werin em li dema veguhastina daneyê di navbera du girêkên torê de bi karanîna protokolên TCP û UDP binihêrin. Bila di her du rewşan de em ê pakêtek winda bikin.
Veguheztina daneyên ne-înteraktîf li ser TCP:Pêkanîna protokola Udp ya pêbawer ji bo .Net

Wekî ku hûn ji diagramê jî dibînin, di bûyera windabûna pakêtê de, TCP dê pakêta wenda teşhîs bike û wê ji şanderê re ragihîne ku jimara beşa winda dipirse.
Veguheztina daneyê bi protokola UDP:Pêkanîna protokola Udp ya pêbawer ji bo .Net

UDP tu gavên tespîtkirina windabûnê nagire. Kontrolkirina xeletiyên veguheztinê di protokola UDP de bi tevahî berpirsiyariya serîlêdanê ye.

Tespîtkirina xeletiyê di protokola TCP-ê de bi sazkirina pêwendiyek bi girêka dawî re, tomarkirina rewşa wê pêwendiyê, nîşankirina hejmara baytên ku di sernavê her pakêtê de têne şandin, û bi karanîna jimareyek pejirandinê agahdarkirina meqbûzan pêk tê.

Digel vê yekê, ji bo baştirkirina performansê (ango ji yek beşê zêdetir bişîne bêyî ku pejirandinek werbigire), protokola TCP bi vî rengî pencereya veguhestinê bikar tîne - hejmara baytên daneya ku şanderê beşê hêvî dike ku bistîne.

Ji bo bêtir agahdarî li ser protokola TCP, binêre rfc 793, ji UDP heta rfc 768ku, bi rastî, ew têne diyarkirin.

Ji jor, diyar e ku ji bo afirandina protokolek gihandina peyama pêbawer li ser UDP (li vir wekî UDP pêbawer), pêdivî ye ku mekanîzmayên veguheztina daneyê yên mîna TCP bicîh bikin. Ango:

  • dewleta girêdanê xilas bike
  • jimareya beşê bikar bînin
  • pakêtên pejirandinê yên taybetî bikar bînin
  • mekanîzmayek pencereyê ya hêsan bikar bînin da ku rêjeya protokolê zêde bikin

Wekî din, hûn hewce ne:

  • sînyala destpêkirina peyamekê, ji bo veqetandina çavkaniyan ji bo girêdanê
  • dawiya peyamekê nîşan bide, da ku peyama wergirtî derbasî serîlêdana jorîn bike û çavkaniyên protokolê berde
  • destûrê bidin protokola pêwendiyê ku mekanîzmaya pejirandina radestkirinê neçalak bike ku wekî UDP "paqij" tevbigere

Sernivîsa UDP ya pêbawer

Bînin bîra xwe ku datagramek UDP di nav datagramek IP-ê de tête girtin. Pakêta UDP ya pêbawer bi guncan di nav datagramek UDP de "pêça" ye.
Veguheztina sernavê UDP-ya pêbawer:Pêkanîna protokola Udp ya pêbawer ji bo .Net

Struktura sernavê UDP-ya pêbawer pir hêsan e:

Pêkanîna protokola Udp ya pêbawer ji bo .Net

  • Ala - alayên kontrolkirina pakêtê
  • MessageType - celebê peyamê ku ji hêla serîlêdanên jorîn ve tê bikar anîn da ku beşdarî peyamên taybetî bibin
  • TransmissionId - hejmara veguheztinê, digel navnîşan û porta wergir, bi yekta girêdanê nas dike
  • PacketNumber - hejmara pakêtê
  • Vebijêrk - Vebijarkên protokola zêde. Di doza pakêta yekem de, ew ji bo nîşankirina mezinahiya peyamê tê bikar anîn

Ala wiha ne:

  • FirstPacket - pakêta yekem a peyamê
  • NoAsk - peyam hewce nake ku mekanîzmayek pejirandinê were çalak kirin
  • LastPacket - pakêta dawî ya peyamê
  • RequestForPacket - pakêta pejirandinê an daxwaza pakêtek winda

Prensîbên giştî yên protokolê

Ji ber ku UDP-ya pêbawer li ser veguheztina peyamê ya garantîkirî di navbera du girêkan de balê dikişîne, divê ew bikaribe bi aliyekî din re têkiliyek saz bike. Ji bo sazkirina têkiliyek, şander pakêtek bi ala FirstPacket re dişîne, bersiva ku tê wê wateyê ku pêwendiyek saz bûye. Hemî pakêtên bersivê, an, bi gotinek din, pakêtên pejirandinê, her gav nirxa qada PacketNumber ji nirxa PacketNumber ya herî mezin a pakêtên ku bi serfirazî hatine wergirtin yek zêde destnîşan dikin. Qada Vebijêrkên ji bo pakêta yekem a şandin mezinahiya peyamê ye.

Mekanîzmayek heman rengî ji bo qedandina pêwendiyek tê bikar anîn. Ala LastPacket li ser pakêta paşîn a peyamê tê danîn. Di pakêta bersivê de, hejmara pakêta paşîn + 1 tê destnîşan kirin, ku ji bo aliyê wergirtinê tê wateya radestkirina serketî ya peyamê.
Diagrama sazkirin û bidawîkirina girêdanê:Pêkanîna protokola Udp ya pêbawer ji bo .Net

Dema ku têkiliyek saz kirin, veguhastina daneyê dest pê dike. Daneyên di blokên pakêtan de têne şandin. Her blok, ji bilî ya paşîn, hejmareke sabît a pakêtan dihewîne. Ew bi mezinahiya pencereya wergirtin/veguheztinê re wekhev e. Dibe ku bloka paşîn a daneyê kêmtir pakêtan hebe. Piştî şandina her blokê, aliyê şandinê li benda pejirandinek radestkirinê an daxwazek ji nû ve radestkirina pakêtên winda disekine, pencereya wergirtinê/veguheztinê vekirî dihêle da ku bersivan werbigire. Piştî wergirtina pejirandina radestkirina blokê, pencereya wergirtin/veguheztinê diguhezîne û bloka din a daneyê tê şandin.

Aliyê wergir pakêtan distîne. Her paketek tê kontrol kirin da ku bibînin ka ew di pencereya veguheztinê de ye. Paket û ducarên ku nakevin pencereyê têne fîltrekirin. Bo Ger mezinahiya pencereyê sabît be û ji bo wergir û şanderê yek be, wê hingê di rewşa ku blokek pakêtan bê windakirin were radest kirin, pace tê veguheztin ku pakêtên bloka daneya din werbigire û pejirandinek radestkirinê tê veguheztin. şandin. Ger pencere di heyama ku ji hêla demjimêra xebatê ve hatî destnîşan kirin de tije nebe, wê hingê dê kontrolek li ser ku pakêt nehatine radest kirin were destpêkirin û dê daxwazên ji nû ve radestkirinê werin şandin.
Diagrama Veguheztinê:Pêkanîna protokola Udp ya pêbawer ji bo .Net

Dem û demjimêrên protokolê

Çend sedem hene ku çima pêwendiyek çênabe. Mînakî, heke partiya wergir negirêdayî be. Di vê rewşê de, dema ku hûn hewl bidin ku pêwendiyek saz bikin, pêwendiya wê ji hêla demjimêr ve tê girtin. Pêkhatina UDP-ya pêbawer du demjimêr bikar tîne da ku demjimêran destnîşan bike. Ya yekem, demjimêra xebatê, tê bikar anîn ku li benda bersivek ji mêvandarê dûr bimîne. Ger ew li aliyê şanderê bişewite, wê hingê pakêta ku hatî şandin ji nû ve tê şandin. Ger demjimêr li wergir biqede, wê hingê kontrolek ji bo pakêtên winda têne kirin û daxwazên ji nû ve radestkirinê têne şandin.

Demjimêra duyemîn hewce ye ku di rewşek nebûna danûstendina di navbera girêkan de pêwendiyê bigire. Ji bo alîyê şanderê, ew tavilê piştî bidawîbûna demjimêra xebatê dest pê dike, û li benda bersivek ji girêka dûr e. Ger di heyama diyarkirî de bersiv neyê dayîn, pêwendî tê qut kirin û çavkanî têne berdan. Ji bo aliyê wergirtinê, demjimêra girtina girêdanê piştî ku demjimêra xebatê du caran qediya dest pê dike. Ev ji bo sîgortakirina li hember windabûna pakêta pejirandinê pêdivî ye. Dema ku demjimêr diqede, têkilî jî bi dawî dibe û çavkanî têne berdan.

Diagrama dewleta veguheztina UDP ya pêbawer

Prensîbên protokolê di makîneyek dewleta bêdawî de têne bicîh kirin, ku her dewletek ji hin mantiqek hilberandina pakêtê berpirsiyar e.
Diagrama Dewleta UDP ya pêbawer:

Pêkanîna protokola Udp ya pêbawer ji bo .Net

Girtî - bi rastî ne dewletek e, ji bo otomatê xalek destpêk û dawî ye. Ji bo dewletê Girtî blokek kontrola veguheztinê tê wergirtin, ku, serverek UDP ya asynchronous bicîh tîne, pakêtan ber bi girêdanên guncan ve dişîne û dest bi pêvajoya dewletê dike.

FirstPacketSending - rewşa destpêkê ya ku pêwendiya derketinê tê de ye dema ku peyam tê şandin.

Di vê rewşê de, pakêta yekem ji bo peyamên normal tê şandin. Ji bo peyamên bêyî pejirandina şandinê, ev yekane dewlet e ku hemî peyam tê şandin.

SendingCycle - rewşa bingehîn ji bo veguhestina pakêtên peyamê.

Derbasbûna wê ji dewletê FirstPacketSending piştî ku pakêta yekem a peyamê hat şandin pêk tê. Di vê rewşê de ye ku hemî pejirandin û daxwazên ji bo veguhestinê têne. Derketin ji wê di du rewşan de mimkun e - di rewşa radestkirina serketî ya peyamê de an jî ji hêla wextê ve.

FirstPacketReceived - rewşa destpêkê ji bo wergirê peyamê.

Ew rastbûna destpêka veguheztinê kontrol dike, strukturên pêwîst diafirîne, û pejirandina wergirtina pakêta yekem dişîne.

Ji bo peyamek ku ji pakêtek yekane pêk tê û bêyî karanîna delîlên radestkirinê hatî şandin, ev yekane dewlet e. Piştî pêvajoyek weha peyamek, girêdan girtî ye.

Damezirandin - dewleta bingehîn ji bo wergirtina pakêtên peyamê.

Ew pakêtan ji hilanîna demkî re dinivîse, windabûna pakêtê kontrol dike, ji bo radestkirina bloka pakêtan û tevahiya peyamê pejirandinê dişîne, û daxwazên ji nû ve radestkirina pakêtên winda dişîne. Di rewşa wergirtina serketî ya tevahiya peyamê de, pêwendiyek diçe nav dewletê Qediya, wekî din, demek derdiket.

Qediya - girtina pêwendiyê di rewşa wergirtina serketî ya tevahiya peyamê de.

Ev rewş ji bo kombûna peyamê û ji bo rewşa ku piştrastkirina radestkirina peyamê di rê de ji şanderê re winda bû hewce ye. Ev rewş ji hêla demkî ve tê derxistin, lê girêdan bi serfirazî girtî tê hesibandin.

Di kodê de kûrtir. yekîneya kontrola veguhestinê

Yek ji hêmanên bingehîn ên UDP-ya pêbawer bloka kontrola veguheztinê ye. Erka vê blokê ev e ku girêdanên heyî û hêmanên alîkar hilîne, pakêtên hatinî li girêdanên têkildar belav bike, navgînek ji bo şandina pakêtan ji pêwendiyekê re peyda bike, û API-ya protokolê bicîh bîne. Bloka kontrola veguheztinê pakêtan ji qata UDP distîne û ji bo pêvajoyê wan dişîne makîneya dewletê. Ji bo wergirtina pakêtan, ew serverek UDP ya asynchronous bicîh tîne.
Hin endamên çîna 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;    	
  //...
}

Pêkanîna servera UDP ya asynchronous:

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

Ji bo her veguheztina peyamê, avahiyek tê afirandin ku agahdariya li ser pêwendiyê vedihewîne. Avahiyeke wisa tê gotin qeyda girêdanê.
Hin endamên çîna 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;
  //...
}

Di kodê de kûrtir. dewletên

Dewlet makîneya dewletê ya protokola UDP ya pêbawer, ku li wir pêvajoya sereke ya pakêtan pêk tê, bicîh dikin. Dersa razber ReliableUdpState ji bo dewletê navgînek peyda dike:

Pêkanîna protokola Udp ya pêbawer ji bo .Net

Tevahiya mantiqê protokolê ji hêla çînên ku li jor hatine pêşkêş kirin, bi hev re digel çînek alîkar ku rêbazên statîk peyda dike, wek mînak, avakirina sernavê ReliableUdp ji qeyda girêdanê, tête bicîh kirin.

Dûv re, em ê bi hûrgulî pêkanîna rêbazên navberê yên ku algorîtmayên bingehîn ên protokolê diyar dikin binirxînin.

Rêbaza DisposeByTimeout

Rêbaza DisposeByTimeout ji bo berdana çavkaniyên pêwendiyê piştî demek û ji bo îşaretkirina gihandina peyama serketî/neserkeftî berpirsiyar e.
ReliableUdpState.DisposeByTimeout:

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

Ew tenê di dewletê de derbas dibe Qediya.
Completed.DisposeByTimeout:

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

Rêbaza ProcessPackets

Rêbaza ProcessPackets ji bo pêvajoyek zêde ya pakêtek an pakêtan berpirsiyar e. Rasterast an bi riya demjimêrek li benda pakêtê tê gazî kirin.

Kêrhat Damezirandin rêbaz tê rijandin û ji bo kontrolkirina pakêtên winda û derbasbûna dewletê berpirsiyar e Qediya, di doza wergirtina pakêta dawîn û derbaskirina kontrolek serketî
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);
  }
}

Kêrhat SendingCycle ev rêbaz tenê li ser demjimêrek tê gotin, û berpirsiyar e ji nû ve şandina peyama paşîn, û hem jî çalakkirina demjimêra girtina girêdanê.
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);
}

Kêrhat Qediya rêbaz demjimêra xebitandinê disekine û peyamê ji aboneyan re dişîne.
Completed.ProcessPackets:

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

Rêbaza ReceivePacket

Kêrhat FirstPacketReceived Erka sereke ya rêbazê ev e ku diyar bike ka pakêta yekem a peyamê bi rastî gihîştiye navberê, û hem jî berhevkirina peyamek ku ji yek pakêtê pêk tê.
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);
  }
}

Kêrhat SendingCycle ev rêbaz ji bo qebûlkirina pejirandinên radestkirinê û daxwazên ji nû ve veguheztinê tê derbas kirin.
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));
}

Kêrhat Damezirandin di rêbaza ReceivePacket de, xebata sereke ya berhevkirina peyamek ji pakêtên hatinî pêk tê.
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);
  }
}

Kêrhat Qediya tenê karê rêbazê ew e ku ji nû ve pejirandina radestkirina serketî ya peyamê bişîne.
Completed.ReceivePacket:

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

Rêbaza pakêtê bişînin

Kêrhat FirstPacketSending ev rêbaz yekem pakêta daneyê dişîne, an jî, heke peyam ne hewceyî pejirandina radestiyê be, hemî peyamê dişîne.
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);
}

Kêrhat SendingCycle di vê rêbazê de, blokek pakêtan tê şandin.
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 );
  }
}

Di kodê de kûrtir. Çêkirin û Damezrandina Têkiliyan

Naha ku me dewletên bingehîn û awayên ku ji bo birêvebirina dewletan têne bikar anîn dîtin, werin em çend mînakan bişkînin ka protokol çawa bi hûrgulî dixebite.
Diagrama veguheztina daneyê di bin şert û mercên normal de:Pêkanîna protokola Udp ya pêbawer ji bo .Net

Bi berfirehî li ser afirandinê bifikirin qeyda girêdanê ji bo girêdan û şandina pakêta yekem. Veguheztin her gav ji hêla serîlêdana ku bangî peyama şandina API-ê dike ve tê destpêkirin. Dûv re, rêbaza StartTransmission ya bloka kontrolkirina veguheztinê tê gazî kirin, ku veguheztina daneya ji bo peyama nû dest pê dike.
Afirandina pêwendiyek derketinê:

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

Di şandina pakêta yekem (dewleta 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);
}

Piştî şandina pakêta yekem, şander dikeve nav dewletê SendingCycle - li benda pejirandina radestkirina pakêtê bisekinin.
Aliyê wergir, bi rêbaza EndReceive bikar tîne, pakêta şandî distîne, nû diafirîne qeyda girêdanê û vê pakêtê, bi sernavek pêş-parçekirî, ji bo pêvajoykirinê derbasî rêbaza ReceivePacket ya dewletê dike. FirstPacketReceived
Afirandina pêwendiyek li aliyê wergirtinê:

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

Wergirtina pakêta yekem û şandina pejirandinê (dewleta 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);
  }
}

Di kodê de kûrtir. Girtina pêwendiyê li ser wextê

Desthilatdariya demdirêj beşek girîng a UDP-ya pêbawer e. Mînakek binihêrin ku tê de girêkek navîn têk çû û radestkirina daneyê di her du aliyan de ne gengaz bû.
Diagram ji bo girtina pêwendiyek ji hêla demdirêjiyê ve:Pêkanîna protokola Udp ya pêbawer ji bo .Net

Wekî ku ji diagramê jî tê dîtin, demjimêra xebata şander piştî şandina bloka pakêtan tavilê dest pê dike. Ev di rêbaza SendPacket ya dewletê de dibe SendingCycle.
Çalakkirina demjimêra xebatê (Rewşa SendingCycle):

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

Demjimêrên demjimêr têne danîn dema ku pêwendiyek tê çêkirin. KurtTimerPerioda xwerû 5 saniye ye. Di nimûneyê de, ew li ser 1,5 çirkeyan tête danîn.

Ji bo pêwendiyek hatî, demjimêr piştî wergirtina pakêta daneya paşîn dest pê dike, ev di rêbaza ReceivePacket ya dewletê de dibe Damezirandin
Çalakkirina demjimêra xebatê (dewleta kombûnê):

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

Dema ku li benda demjimêra xebatê ye, ti pakêt nehatine ser pêwendiya hatinê. Demjimêr çû û gazî rêbaza ProcessPackets kir, ku li wir pakêtên winda hatin dîtin û daxwazên ji nû ve radestkirinê ji bo cara yekem hatin şandin.
Daxwazên şandina ji nû ve şandin (dewleta kombûnê):

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

Guherbara TimerSecondTry tê danîn rast. Ev guhêrbar ji nû ve destpêkirina demjimêra xebatê berpirsiyar e.

Ji aliyê şanderê ve, demjimêra xebatê jî tê dest pê kirin û pakêta şandî ya paşîn ji nû ve tê şandin.
Çalakkirina demjimêra girtina girêdanê (Rewşa SendingCycle):

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

Piştî wê, demjimêra nêzîkbûna pêwendiyê di pêwendiya derketinê de dest pê dike.
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);
}

Demjimêra nêzîkbûna pêwendiyê ji hêla xwerû ve 30 saniye ye.

Piştî demek kurt, demjimêra xebatê ya li aliyê wergir dîsa dişewite, daxwaz ji nû ve têne şandin, piştî ku demjimêra girtina girêdanê ji bo pêwendiya hatî dest pê dike.

Dema ku demjimêrên nêzîk dişewitin, hemî çavkaniyên her du tomarên pêwendiyê têne berdan. Şandkar têkçûna radestkirinê ji serîlêdana jorîn rapor dike (Binêre API-ya UDP ya pêbawer).
Rakirina çavkaniyên tomara girêdanê:

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

Di kodê de kûrtir. Veguheztina daneyan vedigere

Di bûyera windabûna pakêtê de diyagrama veguheztina daneyê:Pêkanîna protokola Udp ya pêbawer ji bo .Net

Wekî ku jixwe di girtina pêwendiyê de li ser wextê hatî nîqaş kirin, dema ku demjimêra xebatê qediya, wergir dê pakêtên winda kontrol bike. Di rewşa windabûna pakêtê de, navnîşek jimara pakêtên ku negihiştine wergir dê were berhev kirin. Van jimareyan têkevin nav rêza LostPackets ya pêwendiyek taybetî, û daxwazên ji nû ve radestkirinê têne şandin.
Daxwazên şandina ji nû ve radestkirina pakêtan (dewleta kombûnê):

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

Dê şander daxwaza ji nû ve radestkirinê qebûl bike û pakêtên winda bişîne. Hêjayî gotinê ye ku di vê gavê de şander berê demjimêra girtina pêwendiyê dest pê kiriye û, dema daxwazek tê wergirtin, ew ji nû ve tê vegerandin.
Ji nû ve şandina pakêtên winda (dewleta 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));
}

Pakêta şandî (di diagramê de pakêta #3) ji hêla pêwendiya gihîştî ve tê wergirtin. Kontrolek tê kirin da ku were dîtin ka pencereya wergirtinê tije ye û veguheztina daneya normal tê vegerandin.
Kontrolkirina lêdanên di pencereya wergirtinê de (dewleta berhevkirinê):

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

Pêbawer UDP API

Ji bo ku bi protokola veguheztina daneyê re têkilî daynin, çînek Reliable Udp-ya vekirî heye, ku li ser bloka kontrolkirina veguheztinê pêçek e. Li vir endamên herî girîng ên polê hene:

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

Peyam bi abonetiyê têne wergirtin. Ji bo rêbaza vegerandina bangê îmzeya delege:

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

Mesaj:

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

Ji bo aboneya celebek peyamek taybetî û/an şanderek taybetî, du pîvanên vebijarkî têne bikar anîn: ReliableUdpMessageTypes messageType û IPEndPoint ipEndPoint.

Cûreyên peyamê:

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

Peyam bi asynkronî tê şandin; ji bo vê yekê, protokol modelek bernameyek asynchronous bicîh tîne:

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

Encama şandina peyamek dê rast be - heke peyam bi serfirazî gihîştiye wergir û xelet - heke girêdan ji hêla demdirêj ve hatî girtin:

public bool EndSendMessage(IAsyncResult asyncResult)

encamê

Di vê gotarê de pir tişt nehatiye vegotin. Mekanîzmayên hevberdana mijarê, îstîsna û birêvebirina xeletiyan, pêkanîna rêbazên şandina peyamên asynkron. Lê bingeha protokolê, ravekirina mantiqa ji bo pêvajokirina pakêtan, damezrandina pêwendiyê, û rêgirtina li dema derbasbûnê, divê ji we re zelal be.

Guhertoya destnîşankirî ya protokola radestkirina pêbawer têra xwe zexm û maqûl e ku hewcedariyên berê diyarkirî bicîh bîne. Lê ez dixwazim lê zêde bikim ku pêkanîna diyarkirî dikare baştir bibe. Mînakî, ji bo zêdekirina rêgezê û guheztina dînamîkî ya serdemên demjimêrê, mekanîzmayên wekî pencereya hilanînê û RTT dikarin li protokolê werin zêdekirin, di heman demê de dê kêrhatî be ku mekanîzmayek ji bo destnîşankirina MTU di navbera girêkên girêdanê de bicîh bike (lê tenê heke peyamên mezin werin şandin) .

Spas ji bo baldariya we, ez li benda şîrove û şîroveyên we me.

PS Ji bo kesên ku bi hûrguliyan re eleqedar dibin an tenê dixwazin protokolê biceribînin, lînka projeyê li ser GitHube:
Projeya UDP ya pêbawer

Girêdan û gotarên kêrhatî

  1. Taybetmendiya protokola TCP: bi îngilîzî и bi rûsî
  2. Taybetmendiya protokola UDP: bi îngilîzî и bi rûsî
  3. Gotûbêja protokola RUDP: draft-ietf-sigtran-pêbawer-udp-00
  4. Protokola Daneyên pêbawer: rfc 908 и rfc 1151
  5. Pêkanîna hêsan a pejirandina radestkirinê li ser UDP: Bi .NET Û UDP-ê Bi tevayî Kontrola Tora Xwe Bigirin
  6. Gotara ku mekanîzmayên derbasbûna NAT vedibêje: Têkiliya Peer-to-Peer li seranserê Wergerên Navnîşana Torê
  7. Pêkanîna modela bernameya asynchronous: Pêkanîna Modela Bernamesaziya Asynchronous CLR и Meriv çawa şêwaza sêwirana IAsyncResult bicîh tîne
  8. Veguheztina modela bernamesaziya asynchronous bo nimûneya asynchronous-based peywirê (APM di TAP de):
    Programming Asynchronous TPL û Traditional .NET
    Bi Nimûne û Cûreyên Asînkron ên Din re têkilî daynin

Nûvekirin: Spas mayorovp и sidristij ji bo ramana zêdekirina peywirek li navrûyê. Lihevhatina pirtûkxaneyê bi pergalên xebitandinê yên kevn re nayê binpêkirin, ji ber Çarçoveya 4-ê hem servera XP û hem jî 2003 piştgirî dike.

Source: www.habr.com

Add a comment