Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Tha an eadar-lìn air atharrachadh o chionn fhada. Is e aon de phrìomh phròtacalan an eadar-lìn - UDP a chleachdadh le tagraidhean chan ann a-mhàin gus datagrams agus craolaidhean a lìbhrigeadh, ach cuideachd gus ceanglaichean “co-aoisean” a thoirt seachad eadar nodan lìonra. Air sgàth an dealbhadh sìmplidh aige, tha mòran chleachdaidhean gun phlanadh aig a’ phròtacal seo roimhe, ach, chan eil easbhaidhean a ’phròtacail, leithid dìth lìbhrigidh cinnteach, air a dhol à bith an àite sam bith. Tha an artaigil seo a’ toirt cunntas air buileachadh a’ phròtacal lìbhrigidh barrantaichte thairis air UDP.
Clàr-innse:Clàrachadh
Riatanasan Pròtacal
Ceann-cinn earbsach UDP
Prionnsabalan coitcheann a 'phròtacail
Ùinean-ama agus clàran-ama protocol
Diagram stàite tar-chuir earbsach UDP
Nas doimhne a-steach don chòd. aonad smachd tar-chuir
Nas doimhne a-steach don chòd. stàitean

Nas doimhne a-steach don chòd. A’ cruthachadh agus a’ stèidheachadh cheanglaichean
Nas doimhne a-steach don chòd. A 'dùnadh a' cheangail nuair a thig e gu crìch
Nas doimhne a-steach don chòd. Ag ath-nuadhachadh gluasad dàta
API UDP earbsach
co-dhùnadh
Ceanglaichean feumail agus artaigilean

Clàrachadh

Ghabh ailtireachd tùsail an eadar-lìn àite seòlaidh aon-ghnèitheach anns an robh seòladh IP cruinneil agus sònraichte aig gach nód agus b’ urrainn dhaibh conaltradh gu dìreach le nodan eile. A-nis, gu dearbh, tha ailtireachd eadar-dhealaichte aig an eadar-lìn - aon raon de sheòlaidhean IP cruinne agus mòran raointean le seòlaidhean prìobhaideach falaichte air cùl innealan NAT.Anns an ailtireachd seo, is e dìreach innealan anns an àite seòlaidh cruinne as urrainn conaltradh gu furasta le neach sam bith air an lìonra leis gu bheil seòladh IP sònraichte aca a tha comasach air feadh na cruinne. Faodaidh nód air lìonra prìobhaideach ceangal ri nodan eile air an aon lìonra, agus faodaidh e cuideachd ceangal a dhèanamh ri nodan ainmeil eile anns an àite seòlaidh cruinneil. Tha an eadar-obrachadh seo air a choileanadh gu ìre mhòr mar thoradh air uidheamachd eadar-theangachaidh seòladh lìonra. Bidh innealan NAT, leithid routers Wi-Fi, a’ cruthachadh clàran eadar-theangachaidh sònraichte airson ceanglaichean a-mach agus ag atharrachadh seòlaidhean IP agus àireamhan puirt ann am pacaidean. Leigidh seo le ceanglaichean a-mach bhon lìonra phrìobhaideach gu luchd-aoigheachd anns an àite seòlaidh cruinneil. Ach aig an aon àm, mar as trice bidh innealan NAT a’ bacadh a h-uile trafaic a tha a’ tighinn a-steach mura h-eil riaghailtean fa leth air an suidheachadh airson ceanglaichean a-steach.

Tha an ailtireachd seo den eadar-lìn ceart gu leòr airson conaltradh teachdaiche-frithealaidh, far am faod teachdaichean a bhith ann an lìonraidhean prìobhaideach, agus seòladh cruinne aig frithealaichean. Ach tha e a 'cruthachadh dhuilgheadasan airson ceangal dìreach dà nodan eadar diofar lìonraidhean prìobhaideach. Tha ceangal dìreach eadar dà nod cudromach airson tagraidhean co-aoisean leithid tar-chuir guth (Skype), faighinn gu astar air coimpiutair (TeamViewer), no geamannan air-loidhne.

Is e punching toll aon de na dòighean as èifeachdaiche airson ceangal co-aoisean a stèidheachadh eadar innealan air diofar lìonraidhean prìobhaideach. Tha an dòigh seo air a chleachdadh gu cumanta le tagraidhean stèidhichte air protocol UDP.

Ach ma tha feum aig an tagradh agad air lìbhrigeadh cinnteach de dhàta, mar eisimpleir, bidh thu a’ gluasad fhaidhlichean eadar coimpiutairean, bidh mòran dhuilgheadasan ann a bhith a’ cleachdadh UDP leis nach e protocol lìbhrigidh cinnteach a th’ ann an UDP agus nach eil e a’ toirt seachad lìbhrigeadh pacaid ann an òrdugh, eu-coltach ris an TCP pròtacal.

Anns a ’chùis seo, gus dèanamh cinnteach à lìbhrigeadh pacaid cinnteach, feumar protocol còmhdach tagraidh a chuir an gnìomh a bheir seachad an comas-gnìomh riatanach agus a bhios ag obair thairis air UDP.

Tha mi airson a thoirt fa-near sa bhad gu bheil dòigh punching tuill TCP ann airson ceanglaichean TCP a stèidheachadh eadar nodan ann an diofar lìonraidhean prìobhaideach, ach air sgàth dìth taic dha le mòran innealan NAT, mar as trice chan eilear ga mheas mar am prìomh dhòigh air ceangal a dhèanamh. nodan mar sin.

Airson a’ chòrr den artaigil seo, cha chuir mi fòcas ach air buileachadh a’ phròtacal lìbhrigidh cinnteach. Thèid cunntas a thoirt air buileachadh dòigh punching tuill UDP anns na h-artaigilean a leanas.

Riatanasan Pròtacal

  1. Lìbhrigeadh pacaid earbsach air a chuir an gnìomh tro uidheamachd fios-air-ais adhartach (an aithne adhartach ris an canar)
  2. An fheum air gluasad èifeachdach de dhàta mòr, i.e. feumaidh am protocol ath-chraoladh pacaid neo-riatanach a sheachnadh
  3. Bu chòir gum biodh e comasach an uidheamachd dearbhaidh lìbhrigidh a chuir dheth (an comas a bhith ag obair mar phròtacal UDP “fìor-ghlan”)
  4. Comas modh àithne a chuir an gnìomh, le dearbhadh air gach teachdaireachd
  5. Feumaidh an aonad bunaiteach de ghluasad dàta thairis air a 'phròtacal a bhith na theachdaireachd

Tha na riatanasan sin gu ìre mhòr aig an aon àm ris na riatanasan Pròtacal Dàta earbsach a tha air am mìneachadh ann an rfc908 и rfc1151, agus chuir mi earbsa anns na h-inbhean sin nuair a bha mi a’ leasachadh a’ phròtacal seo.

Gus na riatanasan sin a thuigsinn, leig dhuinn sùil a thoirt air àm gluasad dàta eadar dà nod lìonra a ’cleachdadh protocolaidhean TCP agus UDP. Leig anns an dà chùis bidh aon phacaid againn air chall.
Gluasad dàta neo-eadar-ghnìomhach thairis air TCP:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Mar a chì thu bhon dealbh, gun fhios nach caillear a’ phacaid, lorgaidh TCP am pacaid a chaidh air chall agus bheir e cunntas don neach a chuir e le bhith ag iarraidh àireamh na h-earrainn a chaidh air chall.
Gluasad dàta tro phròtacal UDP:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Cha bhith UDP a’ gabhail ceumannan lorg call sam bith. Tha smachd air mearachdan tar-chuir ann am protocol UDP gu tur an urra ris an tagradh.

Tha lorg mearachd anns a’ phròtacal TCP air a choileanadh le bhith a’ stèidheachadh ceangal le nód crìochnachaidh, a’ stòradh staid a’ cheangail sin, a’ comharrachadh an àireamh de bytes a chaidh a chuir a-steach anns gach bann-cinn pacaid, agus a’ cur fios gu cuidhteasan a’ cleachdadh àireamh aithneachaidh.

A bharrachd air an sin, gus coileanadh a leasachadh (ie a’ cur barrachd air aon earrann gun a bhith a’ faighinn aithne), bidh am protocol TCP a’ cleachdadh an uinneag tar-chuir ris an canar - an àireamh de bytes de dhàta a tha dùil aig neach-cuiridh na h-earrainn.

Airson tuilleadh fiosrachaidh mu phròtacal TCP, faic rfc793, bho UDP gu rfc768far a bheil, gu dearbh, tha iad air am mìneachadh.

Bho na tha gu h-àrd, tha e soilleir, gus protocol lìbhrigidh teachdaireachd earbsach a chruthachadh thairis air UDP (air ainmeachadh an-seo mar UDP earbsach), feumar dòighean gluasad dàta coltach ri TCP a chuir an gnìomh. Is e sin:

  • sàbhail staid ceangail
  • cleachdadh àireamhachadh earrainnean
  • cleachd pasganan dearbhaidh sònraichte
  • cleachd inneal uinneag nas sìmplidhe gus gluasad protocol àrdachadh

A bharrachd air an sin, feumaidh tu:

  • comharraich toiseach teachdaireachd, gus goireasan a riarachadh airson a’ cheangail
  • comharraich deireadh teachdaireachd, gus an teachdaireachd a fhuaireadh a chuir chun tagradh suas an abhainn agus goireasan protocol a leigeil ma sgaoil
  • leigeil leis a’ phròtacal ceangail-sònraichte an uidheamachd dearbhaidh lìbhrigidh a chuir dheth gus a bhith ag obair mar UDP “fìor-ghlan”.

Ceann-cinn earbsach UDP

Cuimhnich gu bheil datagram UDP air a chuairteachadh ann an datagram IP. Tha am pasgan UDP earbsach gu h-iomchaidh “air a phasgadh” ann an datagram UDP.
Còmhdach cinn-cinn earbsach UDP:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Tha structar bann-cinn earbsach UDP gu math sìmplidh:

Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

  • Brataichean - brataichean smachd pacaid
  • MessageType - seòrsa teachdaireachd air a chleachdadh le tagraidhean shuas an abhainn gus ballrachd a thoirt do theachdaireachdan sònraichte
  • TransmissionId - tha àireamh an tar-chuir, còmhla ri seòladh agus port an neach a gheibh e, a’ comharrachadh a’ cheangail gu sònraichte
  • Àireamh pacaid - àireamh pacaid
  • Roghainnean - roghainnean protocol a bharrachd. Ann an cùis a 'chiad phacaid, tha e air a chleachdadh gus meud na teachdaireachd a chomharrachadh

Tha na brataichean mar a leanas:

  • FirstPacket - a’ chiad phacaid den teachdaireachd
  • NoAsk - chan fheum an teachdaireachd inneal aithneachaidh gus a chuir an comas
  • LastPacket - am pasgan mu dheireadh den teachdaireachd
  • RequestForPacket - pasgan dearbhaidh no iarrtas airson pasgan caillte

Prionnsabalan coitcheann a 'phròtacail

Leis gu bheil UDP earbsach ag amas air sgaoileadh teachdaireachd cinnteach eadar dà nod, feumaidh e a bhith comasach air ceangal a stèidheachadh leis an taobh eile. Gus ceangal a stèidheachadh, bidh an neach a chuir a-steach pasgan le bratach FirstPacket, agus bidh am freagairt a’ ciallachadh gu bheil an ceangal air a stèidheachadh. Bidh a h-uile pasgan freagairt, no, ann am faclan eile, pacaidean aithneachaidh, an-còmhnaidh a’ suidheachadh luach raon PacketNumber gu aon a bharrachd air an luach PacketNumber as motha de phasganan a fhuaireadh gu soirbheachail. Is e an raon Roghainnean airson a’ chiad phacaid a chaidh a chuir a-steach meud na teachdaireachd.

Tha inneal coltach ris air a chleachdadh gus crìoch a chuir air ceangal. Tha bratach LastPacket suidhichte air a’ phacaid mu dheireadh den teachdaireachd. Anns a 'phacaid freagairt, tha àireamh a' phacaid mu dheireadh + 1 air a chomharrachadh, a tha airson an taobh faighinn a 'ciallachadh lìbhrigeadh soirbheachail air an teachdaireachd.
Stèidheachadh ceangail agus diagram crìochnachaidh:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Nuair a thèid an ceangal a stèidheachadh, tòisichidh gluasad dàta. Tha dàta air a ghluasad ann am blocaichean pacaidean. Anns gach bloc, ach a-mhàin am fear mu dheireadh, tha àireamh stèidhichte de phasganan. Tha e co-ionann ri meud na h-uinneige faighinn / tar-chuir. Is dòcha gu bheil nas lugha de phacaidean aig a’ bhloc dàta mu dheireadh. Às deidh gach bloc a chuir, bidh an taobh cur a’ feitheamh ri dearbhadh lìbhrigidh no iarrtas gus pacaidean caillte ath-lìbhrigeadh, a’ fàgail an uinneag faighinn / tar-chuir fosgailte gus freagairtean fhaighinn. Às deidh dha dearbhadh fhaighinn mu lìbhrigeadh bloca, gluaisidh an uinneag faighinn / tar-chuir agus thèid an ath bhloca dàta a chuir.

Bidh an taobh faighinn a’ faighinn na pacaidean. Bithear a’ sgrùdadh gach pacaid gus faicinn a bheil e taobh a-staigh na h-uinneige tar-chuir. Tha pacaidean agus lethbhreacan nach tuit a-steach don uinneig air an sìoladh a-mach. Air sgàth Ma tha meud na h-uinneige stèidhichte agus an aon rud airson an neach a gheibh e agus an neach a chuir e, an uairsin ma thèid bloc de phasgan a lìbhrigeadh gun chall, thèid an uinneag a ghluasad gus pacaidean den ath bhloca dàta fhaighinn agus tha dearbhadh lìbhrigidh ann. chuir. Mura lìon an uinneag taobh a-staigh na h-ùine a shuidhich an timer obrach, thèid sgrùdadh a thòiseachadh air dè na pacaidean nach deach a lìbhrigeadh agus thèid iarrtasan airson ath-lìbhrigeadh a chuir.
Diagram ath-chraolaidh:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Ùinean-ama agus clàran-ama protocol

Tha grunn adhbharan ann nach urrainnear ceangal a stèidheachadh. Mar eisimpleir, ma tha am pàrtaidh faighinn far loidhne. Anns a 'chùis seo, nuair a thathar a' feuchainn ri ceangal a stèidheachadh, thèid an ceangal a dhùnadh le ùine. Bidh buileachadh earbsach UDP a’ cleachdadh dà timers gus amannan a shuidheachadh. Tha a’ chiad fhear, an timer obrach, air a chleachdadh gus feitheamh ri freagairt bhon òstair iomallach. Ma loisgeas e air taobh an t-seoladair, tha a’ phacaid mu dheireadh a chaidh a chuir a-mach às a chiall. Ma thig an timer gu crìch aig an neach a gheibh e, thèid seic airson pacaidean a chaidh air chall a dhèanamh agus thèid iarrtasan airson ath-lìbhrigeadh a chuir.

Tha feum air an dàrna timer gus an ceangal a dhùnadh ma tha dìth conaltraidh eadar na nodan. Airson taobh an neach-cuiridh, bidh e a’ tòiseachadh sa bhad às deidh don timer obrach tighinn gu crìch, agus a’ feitheamh ri freagairt bhon nód iomallach. Mura h-eil freagairt ann taobh a-staigh na h-ùine ainmichte, thèid an ceangal a thoirt gu crìch agus thèid goireasan a leigeil ma sgaoil. Airson an taobh faighinn, thèid an timer dùnadh ceangail a thòiseachadh às deidh don timer obrach tighinn gu crìch dà uair. Tha seo riatanach gus àrachas an aghaidh call a’ phacaid dearbhaidh. Nuair a thig an timer gu crìch, thèid an ceangal a thoirt gu crìch cuideachd agus thèid goireasan a leigeil ma sgaoil.

Diagram stàite tar-chuir earbsach UDP

Tha prionnsapalan a ’phròtacail air an cur an gnìomh ann an inneal stàite crìochnaichte, agus tha uallach air gach stàit airson loidsig sònraichte de ghiollachd pacaidean.
Diagram Stàite UDP earbsach:

Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

dùinte - chan e fìor stàit a th’ ann, tha e na thoiseach tòiseachaidh is crìochnachaidh airson an automaton. Airson stàite dùinte gheibhear bloc smachd tar-chuir, a bhios, a’ cur an gnìomh frithealaiche UDP asyncronach, a’ cur pacaidean air adhart gu na ceanglaichean iomchaidh agus a’ tòiseachadh air giullachd stàite.

FirstPacketSending - a’ chiad staid anns a bheil an ceangal a-mach nuair a thèid an teachdaireachd a chuir.

Anns an stàit seo, thèid a 'chiad phacaid airson teachdaireachdan àbhaisteach a chuir. Airson teachdaireachdan gun dearbhadh air falbh, is e seo an aon stàit far a bheil an teachdaireachd gu lèir air a chuir.

SendingCycle - suidheachadh bunaiteach airson sgaoileadh pacaidean teachdaireachd.

Eadar-ghluasad thuige bhon stàit FirstPacketSending air a dhèanamh às deidh a’ chiad phacaid den teachdaireachd a chuir. Is ann anns an staid seo a thig a h-uile aithne agus iarrtas airson ath-chraoladh. Tha e comasach fàgail ann an dà chùis - gun fhios nach tèid an teachdaireachd a lìbhrigeadh gu soirbheachail no le ùine a-mach.

A’ chiad phacaid air fhaighinn - a’ chiad staid airson an neach a gheibh an teachdaireachd.

Bidh e a 'dèanamh sgrùdadh air dè cho ceart' sa tha toiseach an tar-chuir, a 'cruthachadh nan structaran riatanach, agus a' cur a-steach aithne gun d 'fhuaireadh a' chiad phacaid.

Airson teachdaireachd anns a bheil aon phacaid agus a chaidh a chuir gun a bhith a’ cleachdadh dearbhadh lìbhrigidh, is e seo an aon stàit. Às deidh a leithid de theachdaireachd a ghiullachd, tha an ceangal dùinte.

A 'cruinneachadh - suidheachadh bunaiteach airson pacaidean teachdaireachd fhaighinn.

Bidh e a’ sgrìobhadh phasganan gu stòradh sealach, a’ dèanamh sgrùdaidhean airson call pacaid, a’ cur a-mach aithne airson a bhith a’ lìbhrigeadh bloc pacaidean agus an teachdaireachd gu lèir, agus a’ cur iarrtasan airson pacaidean caillte ath-lìbhrigeadh. Ma thèid an teachdaireachd gu lèir fhaighinn gu soirbheachail, thèid an ceangal a-steach don stàit crìochnachadh, air dhòigh eile, thig ùine a-mach.

crìochnachadh - dùin an ceangal gun fhios nach fhaighear an teachdaireachd gu lèir gu soirbheachail.

Tha an stàit seo riatanach airson an teachdaireachd a cho-chruinneachadh agus airson a 'chùis nuair a chaidh dearbhadh lìbhrigidh na teachdaireachd a chall air an t-slighe chun an neach a chuir e. Tha an stàit seo air fhàgail le ùine a-mach, ach thathas den bheachd gu bheil an ceangal dùinte gu soirbheachail.

Nas doimhne a-steach don chòd. aonad smachd tar-chuir

Is e aon de na prìomh eileamaidean de UDP earbsach am bloc smachd tar-chuir. Is e obair a’ bhloca seo ceanglaichean gnàthach agus eileamaidean taice a stòradh, pacaidean a thig a-steach a sgaoileadh gu na ceanglaichean co-fhreagarrach, eadar-aghaidh a thoirt seachad airson pacaidean a chuir gu ceangal, agus am protocol API a chuir an gnìomh. Bidh am bloc smachd tar-chuir a’ faighinn pacaidean bhon ìre UDP agus gan cur air adhart chun inneal stàite airson a ghiullachd. Gus pacaidean fhaighinn, bidh e a’ cur an gnìomh frithealaiche UDP asyncronach.
Cuid de bhuill den chlas 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;    	
  //...
}

Cur an gnìomh frithealaiche UDP asyncronach:

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

Airson gach gluasad teachdaireachd, thèid structar a chruthachadh anns a bheil fiosrachadh mun cheangal. Canar structar mar seo clàr ceangail.
Cuid de bhuill den chlas 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;
  //...
}

Nas doimhne a-steach don chòd. stàitean

Bidh stàitean a’ cur an gnìomh inneal stàite a’ phròtacal UDP earbsach, far am bi prìomh ghiollachd phasganan a’ tachairt. Tha an clas eas-chruthach ReliableUdpState a’ toirt seachad eadar-aghaidh airson na stàite:

Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Tha loidsig iomlan a’ phròtacal air a bhuileachadh leis na clasaichean a tha air an taisbeanadh gu h-àrd, còmhla ri clas taiceil a bheir seachad dòighean statach, leithid, mar eisimpleir, a bhith a’ togail bann-cinn ReliableUdp bhon chlàr ceangail.

An ath rud, beachdaichidh sinn gu mionaideach air buileachadh nan dòighean eadar-aghaidh a bhios a ’dearbhadh algorithms bunaiteach a’ phròtacal.

Modh DisposeByTimeout

Tha an dòigh DisposeByTimeout an urra ri goireasan ceangail a leigeil ma sgaoil às deidh ùine a-muigh agus airson lìbhrigeadh teachdaireachd soirbheachail / neo-shoirbheachail a chomharrachadh.
ReliableUdpState.DisposeByTimeout:

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

Chan eil e ach air a chuir thairis anns an stàit crìochnachadh.
Crìochnaichte.DisposebyTimeout:

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

Modh ProcessPackets

Tha uallach air modh ProcessPackets airson giullachd a bharrachd air pasgan no pacaidean. Air a ghairm gu dìreach no tro timer feitheimh pacaid.

comasach A 'cruinneachadh tha an dòigh air a dhol thairis air agus tha e an urra ri sgrùdadh a dhèanamh airson pacaidean caillte agus gluasad chun stàite crìochnachadh, gun fhios nach faigh thu am pasgan mu dheireadh agus a dhol seachad air seic soirbheachail
A' cruinneachadh.PacketsProcess:

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

comasach SendingCycle chan eil an dòigh seo air a ghairm ach air timer, agus tha e an urra ris an teachdaireachd mu dheireadh a chuir air ais, a bharrachd air a bhith a’ comasachadh an timer dlùth ceangail.
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);
}

comasach crìochnachadh bidh an dòigh a’ stad an timer ruith agus a’ cur an teachdaireachd gu na fo-sgrìobhaichean.
Completed.ProcessPackets:

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

Modh Faigh Packet

comasach A’ chiad phacaid air fhaighinn Is e prìomh obair an dòigh faighinn a-mach an do ràinig a’ chiad phasgan teachdaireachd an eadar-aghaidh, agus cuideachd teachdaireachd a chruinneachadh anns a bheil aon phacaid.
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);
  }
}

comasach SendingCycle tha an dòigh seo air a chuir an aghaidh gus gabhail ri aithne lìbhrigidh agus iarrtasan ath-chraolaidh.
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));
}

comasach A 'cruinneachadh anns an dòigh ReceivePacket, bidh am prìomh obair ann a bhith a’ cruinneachadh teachdaireachd bho phasganan a tha a’ tighinn a-steach.
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);
  }
}

comasach crìochnachadh is e an aon obair a th’ aig a’ mhodh ath-aithneachadh a chuir a-mach mu lìbhrigeadh soirbheachail na teachdaireachd.
Crìochnaichte.ReceivePacket:

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

Cuir Modh Pacaid

comasach FirstPacketSending bidh an dòigh seo a 'cur a' chiad phacaid dàta, no, mura h-eil feum air dearbhadh lìbhrigidh, an teachdaireachd gu lèir.
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);
}

comasach SendingCycle san dòigh seo, thèid bloc de phasgan a chuir.
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 );
  }
}

Nas doimhne a-steach don chòd. A’ cruthachadh agus a’ stèidheachadh cheanglaichean

A-nis gu bheil sinn air na stàitean bunaiteach fhaicinn agus na dòighean a thathas a’ cleachdadh airson stàitean a làimhseachadh, brisidh sinn sìos beagan eisimpleirean air mar a tha am protocol ag obair ann am beagan nas mionaidiche.
Diagram tar-chuir dàta fo chumhachan àbhaisteach:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Beachdaich gu mionaideach air an cruthachadh clàr ceangail gus a 'chiad phacaid a cheangal agus a chuir. Bidh an gluasad an-còmhnaidh air a thòiseachadh leis an tagradh a chanas an teachdaireachd cuir API. An uairsin, thèid an dòigh StartTransmission den bhloc smachd tar-chuir a chleachdadh, a thòisicheas air sgaoileadh dàta airson an teachdaireachd ùr.
A’ cruthachadh ceangal a-mach:

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

A’ cur a’ chiad phacaid (FirstPacketSending state):

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

Às deidh a’ chiad phacaid a chuir, thig an neach a chuir a-steach don stàit SendingCycle - feitheamh ri dearbhadh air lìbhrigeadh pacaid.
Bidh an taobh faighinn, a’ cleachdadh modh EndReceive, a’ faighinn a’ phacaid a chaidh a chuir, a’ cruthachadh fear ùr clàr ceangail agus a’ dol seachad air a’ phacaid seo, le bann-cinn ro-phàighte, gu modh ReceivePacket na stàite airson a ghiullachd A’ chiad phacaid air fhaighinn
A 'cruthachadh ceangal air an taobh faighinn:

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

A’ faighinn a’ chiad phacaid agus a’ cur aithne (staid 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);
  }
}

Nas doimhne a-steach don chòd. A 'dùnadh a' cheangail nuair a thig e gu crìch

Tha làimhseachadh ùine-ama na phàirt chudromach de UDP earbsach. Beachdaich air eisimpleir anns an do dh'fhàillig nód eadar-mheadhanach agus nach robh e do-dhèanta lìbhrigeadh dàta anns gach taobh.
Diagram airson ceangal a dhùnadh ro àm a-muigh:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Mar a chithear bhon diagram, bidh timer obrach an neach a chuir a-steach a’ tòiseachadh sa bhad às deidh bloc de phasgan a chuir. Bidh seo a’ tachairt ann am modh SendPacket na stàite SendingCycle.
A’ comasachadh an timer obrach (staid SendingCycle):

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

Tha na h-amannan timer air an suidheachadh nuair a thèid an ceangal a chruthachadh. Is e an ShortTimerPeriod àbhaisteach 5 diogan. Anns an eisimpleir, tha e air a shuidheachadh gu 1,5 diogan.

Airson ceangal a tha a’ tighinn a-steach, bidh an timer a’ tòiseachadh às deidh dha am pasgan dàta mu dheireadh a thig a-steach fhaighinn, bidh seo a’ tachairt ann am modh ReceivePacket na stàite A 'cruinneachadh
A’ comasachadh an timer obrach (staid cruinneachaidh):

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

Cha do ràinig barrachd phasganan an ceangal a bha a’ tighinn a-steach fhad ‘s a bha iad a’ feitheamh ris an timer obrach. Dh’ fhalbh an timer agus ghairm e am modh ProcessPackets, far an deach na pacaidean caillte a lorg agus chaidh iarrtasan ath-lìbhrigidh a chuir airson a’ chiad uair.
A’ cur iarrtasan ath-lìbhrigidh (Stàit cruinneachaidh):

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

Tha an caochladair TimerSecondTry air a shuidheachadh gu fìor. Tha an caochladair seo an urra ri ath-thòiseachadh an timer obrach.

Air taobh an neach a chuir e, tha an timer obrach cuideachd air a phiobrachadh agus tha am pasgan mu dheireadh a chaidh a chuir a-mach às a dhèidh.
A’ comasachadh timer dùin ceangail (staid SendingCycle):

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

Às deidh sin, tòisichidh an timer dlùth ceangail anns a ’cheangal a tha a’ dol a-mach.
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);
}

Is e an ùine crìochnachaidh ceangail dùnadh 30 diogan gu bunaiteach.

Às deidh ùine ghoirid, bidh an timer obrach air taobh an neach-faighinn a ’losgadh a-rithist, thèid iarrtasan a chuir a-rithist, agus às deidh sin tòisichidh an timer dlùth ceangail airson a’ cheangal a tha a ’tighinn a-steach

Nuair a thèid na timers dlùth a losgadh, thèid a h-uile goireas den dà chlàr ceangail fhoillseachadh. Bidh an neach-cuiridh ag aithris air fàiligeadh lìbhrigidh don tagradh shuas an abhainn (faic Reliable UDP API).
A’ leigeil ma sgaoil goireasan clàr ceangail:

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

Nas doimhne a-steach don chòd. Ag ath-nuadhachadh gluasad dàta

Diagram ath-bheothachaidh tar-chuir dàta gun fhios nach caillear pacaid:Cur an gnìomh a’ phròtacal Udp earbsach airson .Net

Mar a chaidh a dheasbad mu thràth ann a bhith a’ dùnadh a’ cheangail aig àm a-muigh, nuair a thig an timer obrach gu crìch, nì an neach-glacaidh sùil airson pacaidean caillte. Ma thèid pacaid a chall, thèid liosta a chuir ri chèile den àireamh phasgan nach do ràinig an neach a fhuair e. Tha na h-àireamhan sin air an cuir a-steach don raon LostPackets de cheangal sònraichte, agus thèid iarrtasan airson ath-lìbhrigeadh a chuir.
A’ cur iarrtasan gu pacaidean ath-lìbhrigeadh (stàit cruinneachaidh):

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

Gabhaidh an neach-cuiridh ris an iarrtas ath-lìbhrigidh agus cuiridh e na pacaidean a tha a dhìth. 'S fhiach toirt fa-near gu bheil aig an àm seo tha an neach-cuiridh air tòiseachadh air a' cheangal dlùth timer agus, nuair a gheibhear iarrtas, tha e air ath-shuidheachadh.
A’ cur air ais pacaidean caillte (staid 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));
}

Gheibh an ceangal a tha a’ tighinn a-steach a’ phacaid ath-chuinge (pacaid #3 san diagram). Thèid sgrùdadh a dhèanamh gus faicinn a bheil an uinneag faighinn làn agus a bheil tar-chuir dàta àbhaisteach air ath-nuadhachadh.
A’ sgrùdadh airson buillean san uinneag faighinn (staid cruinneachaidh):

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

API UDP earbsach

Gus eadar-obrachadh leis a’ phròtacal gluasad dàta, tha clas Udp earbsach fosgailte, a tha na chòmhdach thairis air a’ bhloc smachd gluasaid. Seo na buill as cudromaiche den chlas:

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

Gheibhear teachdaireachdan tro fho-sgrìobhadh. Ainm-sgrìobhte riochdaire airson modh gairm air ais:

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

Gus ballrachd a thoirt do sheòrsa teachdaireachd sònraichte agus / no neach-cuiridh sònraichte, thathas a’ cleachdadh dà pharamadair roghainneil: ReliableUdpMessageTypes messageType agus IPEndPoint ipEndPoint.

Seòrsa brathan:

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

Tha an teachdaireachd air a chuir gu co-shìnte; airson seo, tha am protocol a’ cur an gnìomh modal prògramadh asyncronach:

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

Bidh toradh teachdaireachd a chuir a-mach fìor - ma shoirbhich leis an teachdaireachd an neach a fhuair e agus gu bheil e meallta - ma chaidh an ceangal a dhùnadh ro àm a-muigh:

public bool EndSendMessage(IAsyncResult asyncResult)

co-dhùnadh

Cha deach mòran a mhìneachadh san artaigil seo. Uidheaman maidsidh snàithlean, eisgeachd agus làimhseachadh mhearachdan, buileachadh dhòighean cur teachdaireachd asyncronach. Ach bu chòir cridhe a’ phròtacal, an tuairisgeul air an loidsig airson giullachd phasganan, stèidheachadh ceangail, agus làimhseachadh amannan a-mach, a bhith soilleir dhut.

Tha an dreach dearbhte den phròtacal lìbhrigidh earbsach làidir agus sùbailte gu leòr gus coinneachadh ris na riatanasan a chaidh a mhìneachadh roimhe. Ach tha mi airson a chuir ris gum faodar am buileachadh a chaidh a mhìneachadh a leasachadh. Mar eisimpleir, gus trochur a mheudachadh agus amannan timer atharrachadh gu dinamach, faodar uidheamachdan leithid uinneag sleamhnachaidh agus RTT a chuir ris a’ phròtacal, bidh e feumail cuideachd inneal a chuir an gnìomh gus an MTU a dhearbhadh eadar nodan ceangail (ach dìreach ma thèid teachdaireachdan mòra a chuir a-steach. ).

Tapadh leibh airson d’ aire, tha mi a’ coimhead air adhart ri do bheachdan agus do bheachdan.

PS Dhaibhsan aig a bheil ùidh anns an fhiosrachadh no dìreach airson deuchainn a dhèanamh air a’ phròtacal, an ceangal ris a ’phròiseact air GitHube:
Pròiseact UDP earbsach

Ceanglaichean feumail agus artaigilean

  1. Sònrachadh protocol TCP: ann am Beurla и ann an Ruisis
  2. Sònrachadh protocol UDP: ann am Beurla и ann an Ruisis
  3. Còmhradh air protocol an RUDP: dreach-ietf-sigtran-earbsach-udp-00
  4. Pròtacal dàta earbsach: rfc908 и rfc1151
  5. Buileachadh sìmplidh air dearbhadh lìbhrigidh thairis air UDP: Gabh smachd iomlan air an lìonradh agad le .NET Agus UDP
  6. Artaigil a’ toirt cunntas air dòighean slighe NAT: Conaltradh co-aoisean air feadh eadar-theangairean seòlaidhean lìonra
  7. Cur an gnìomh modal prògramadh asyncronach: Cur an gnìomh Modail Prògramadh Asyncronach CLR и Mar a chuireas tu pàtran dealbhaidh IAsyncResult an gnìomh
  8. A’ giùlain am modal prògramadh asyncronach chun phàtran asyncronach stèidhichte air gnìomh (APM ann an TAP):
    TPL agus Prògramadh Asyncronach .NET Traidiseanta
    Eadar-obrachadh le pàtrain agus seòrsaichean asyncronach eile

Ùrachadh: Tapadh leibh àrd-uachdar и sidristij airson am beachd gnìomh a chuir ris an eadar-aghaidh. Chan eilear a’ briseadh co-chòrdalachd an leabharlainn le seann shiostaman obrachaidh, oir Tha an 4mh frèam a’ toirt taic do gach cuid frithealaiche XP agus 2003.

Source: www.habr.com

Cuir beachd ann