Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

D'athraigh an tIdirlíon i bhfad ó shin. Ceann de phríomhphrótacail an Idirlín - úsáidtear UDP ag feidhmchláir, ní hamháin chun datagrams agus craoltaí a sheachadadh, ach freisin chun naisc "piara-go-piara" a sholáthar idir nóid líonra. Mar gheall ar a dhearadh simplí, tá go leor úsáidí neamhphleanáilte ag an bprótacal seo, áfach, níl easnaimh an phrótacail, mar shampla an easpa seachadta ráthaithe, imithe in áit ar bith. Déanann an t-alt seo cur síos ar chur i bhfeidhm an phrótacail seachadta ráthaithe thar an UDP.
Clár ábhair:Iontráil
Ceanglais Phrótacail
Ceanntásc iontaofa UDP
Prionsabail ghinearálta an phrótacail
Teorainneacha ama agus amadóirí prótacail
Léaráid iontaofa staid tarchurtha UDP
Níos doimhne isteach an cód. aonad rialaithe tarchurtha
Níos doimhne isteach an cód. stáit

Níos doimhne isteach an cód. Naisc a Chruthú agus a Bhunú
Níos doimhne isteach an cód. An nasc a dhúnadh ar an am istigh
Níos doimhne isteach an cód. Aistriú sonraí a athbhunú
API UDP iontaofa
Conclúid
Naisc úsáideacha agus earraí....

Iontráil

Ghlac ailtireacht bhunaidh an Idirlín spás seolta aonchineálach ina raibh seoladh IP domhanda agus uathúil ag gach nód agus go bhféadfadh siad cumarsáid dhíreach a dhéanamh le nóid eile. Anois, go deimhin, tá ailtireacht dhifriúil ag an Idirlíon - réimse amháin de sheoltaí IP domhanda agus go leor réimsí le seoltaí príobháideacha i bhfolach taobh thiar de ghléasanna NAT.San ailtireacht seo, ní féidir ach le gléasanna sa spás seoltaí domhanda cumarsáid a dhéanamh go héasca le haon duine ar an líonra toisc go bhfuil seoladh IP uathúil atá in-athraithe ar fud an domhain acu. Is féidir le nód ar líonra príobháideach nascadh le nóid eile ar an líonra céanna, agus féadann sé nascadh le nóid aitheanta eile sa spás seoltaí domhanda. Baintear an t-idirghníomhú seo amach go príomha mar gheall ar mheicníocht aistriúcháin seoltaí líonra. Cruthaíonn gléasanna NAT, ar nós ródairí Wi-Fi, iontrálacha táblaí aistriúcháin speisialta do naisc amach agus modhnaítear seoltaí IP agus uimhreacha poirt i bpacáistí. Ligeann sé seo naisc amach ón líonra príobháideach le hóstach sa spás seoltaí domhanda. Ach ag an am céanna, is gnách go gcuireann feistí NAT bac ar gach trácht isteach mura bhfuil rialacha ar leith leagtha síos maidir le naisc isteach.

Tá an ailtireacht seo den Idirlíon ceart go leor le haghaidh cumarsáide cliant-freastalaí, áit ar féidir le cliaint a bheith i líonraí príobháideacha, agus tá seoladh domhanda ag freastalaithe. Ach cruthaíonn sé deacrachtaí maidir le nasc díreach dhá nóid idir éagsúla líonraí príobháideacha. Tá nasc díreach idir dhá nód tábhachtach d’fheidhmchláir phiara le piaraí amhail tarchur gutha (Skype), cianrochtain a fháil ar ríomhaire (TeamViewer), nó cearrbhachas ar líne.

Ar cheann de na modhanna is éifeachtaí chun nasc piara le piaraí a bhunú idir feistí ar líonraí príobháideacha éagsúla, tugtar polladh poll air. Úsáidtear an teicníocht seo go coitianta le hiarratais atá bunaithe ar phrótacal an UDP.

Ach má éilíonn d’iarratas seachadadh ráthaithe sonraí, mar shampla, aistríonn tú comhaid idir ríomhairí, ansin beidh go leor deacrachtaí ag baint úsáide as UDP toisc nach prótacal seachadta ráthaithe é UDP agus nach soláthraíonn sé seachadadh paicéid in ord, murab ionann agus an TCP. prótacal.

Sa chás seo, chun seachadadh paicéid ráthaithe a chinntiú, ní mór prótacal ciseal iarratais a chur i bhfeidhm a sholáthraíonn an fheidhmiúlacht riachtanach agus a oibríonn thar UDP.

Ba mhaith liom a thabhairt faoi deara ar an bpointe boise go bhfuil teicníc pollta poll TCP ann chun naisc TCP a bhunú idir nóid i líonraí príobháideacha éagsúla, ach mar gheall ar an easpa tacaíochta dó ó go leor feistí NAT, de ghnáth ní mheastar é mar an príomhbhealach chun ceangal a dhéanamh. nóid den sórt sin.

Don chuid eile den alt seo, díreoidh mé ar chur i bhfeidhm an phrótacail seachadta ráthaithe amháin. Déanfar cur síos ar chur i bhfeidhm teicníc pollta poll an UDP sna hailt seo a leanas.

Ceanglais Phrótacail

  1. Seachadadh paicéad iontaofa curtha i bhfeidhm trí mheicníocht aiseolais dhearfach (admháil dhearfach mar a thugtar air)
  2. An gá atá le haistriú éifeachtach sonraí móra, i.e. ní mór don phrótacal athsheoladh paicéid gan ghá a sheachaint
  3. Ba cheart go bhféadfaí an sásra deimhnithe seachadta a chur ar ceal (an cumas feidhmiú mar phrótacal UDP "íon")
  4. Cumas mód ordaithe a chur i bhfeidhm, le deimhniú gach teachtaireachta
  5. Ní mór teachtaireacht a bheith san aonad bunúsach aistrithe sonraí thar an bprótacal

Tagann na ceanglais sin den chuid is mó le ceanglais an Phrótacail Sonraí Iontaofa a bhfuil cur síos orthu in RFC 908 и RFC 1151, agus bhraith mé ar na caighdeáin sin agus an prótacal seo á fhorbairt.

Chun na ceanglais seo a thuiscint, déanaimis féachaint ar uainiú an aistrithe sonraí idir dhá nód líonra ag baint úsáide as prótacail TCP agus UDP. Sa dá chás beidh paicéad amháin caillte againn.
Aistriú sonraí neamh-idirghníomhacha thar TCP:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Mar a fheiceann tú ón léaráid, ar eagla go gcailltear paicéad, braithfidh TCP an paicéad caillte agus tuairisceoidh sé don seoltóir é trí uimhir na míre caillte a iarraidh.
Aistriú sonraí trí phrótacal UDP:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Ní ghlacann UDP aon chéimeanna braite caillteanais. Is é an t-iarratas go hiomlán atá freagrach as earráidí tarchuir a rialú sa phrótacal UDP.

Baintear amach earráidí a bhrath sa phrótacal TCP trí nasc a bhunú le nód deiridh, staid an naisc sin a stóráil, líon na mbeart a seoladh i gceannteideal gach paicéad a chur in iúl, agus fáltais a fhógairt ag baint úsáide as uimhir admhála.

Ina theannta sin, chun feidhmíocht a fheabhsú (i.e. níos mó ná mír amháin a sheoladh gan admháil a fháil), úsáideann an prótacal TCP an fhuinneog tarchuir mar a thugtar air - líon na mbeart sonraí a bhfuil seoltóir na míre ag súil lena fháil.

Chun tuilleadh eolais a fháil faoin bprótacal TCP, féach RFC 793, ón UDP go RFC 768i gcás, i ndáiríre, tá siad sainithe.

Ón méid thuas, tá sé soiléir, chun prótacal seachadta teachtaireachta iontaofa a chruthú thar UDP (dá ngairfear anseo feasta UDP iontaofa), ceanglaítear sásraí aistrithe sonraí a chur i bhfeidhm cosúil le TCP. Eadhon:

  • sábháil staid nasc
  • úsáid a bhaint as uimhriú míre
  • bain úsáid as pacáistí dearbhaithe speisialta
  • meicníocht fuinneoige simplithe a úsáid chun tréchur prótacail a mhéadú

Ina theannta sin, ní mór duit:

  • cuir tús le teachtaireacht a chur in iúl, chun acmhainní a leithdháileadh don nasc
  • comhartha deireadh teachtaireachta, chun an teachtaireacht a fuarthas a chur ar aghaidh chuig an iarratas in aghaidh an tsrutha agus scaoileadh acmhainní prótacal
  • cead a thabhairt don phrótacal a bhaineann go sonrach le nasc an mheicníocht dearbhaithe seachadta a dhíchumasú chun feidhmiú mar UDP “íon”.

Ceanntásc iontaofa UDP

Thabhairt chun cuimhne go bhfuil datagram UDP cuimsithe i datagram IP. Tá an paicéad UDP iontaofa "fillte" go cuí i datagram UDP.
Iontaofa ceanntásca UDP iontaofa:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Tá struchtúr an cheanntásc Iontaofa UDP simplí go leor:

Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

  • Bratacha - bratacha rialaithe pacáiste
  • MessageType - cineál teachtaireachta a úsáideann feidhmchláir réamhtheachtacha chun síntiús a íoc le teachtaireachtaí ar leith
  • TransmissionId - aithníonn uimhir an tarchuir, mar aon le seoladh agus calafort an fhaighteora, an nasc go huathúil
  • PacketNumber - uimhir phaicéad
  • Roghanna - roghanna prótacal breise. I gcás an chéad phaicéad, úsáidtear é chun méid na teachtaireachta a chur in iúl

Seo a leanas na bratacha:

  • FirstPacket - an chéad phaicéad den teachtaireacht
  • NoAsk - ní gá meicníocht admhála a bheith cumasaithe don teachtaireacht
  • LastPacket - an paicéad deireanach den teachtaireacht
  • RequestForPacket - paicéad deimhnithe nó iarratas ar phaicéad caillte

Prionsabail ghinearálta an phrótacail

Ós rud é go bhfuil UDP Iontaofa dírithe ar tharchur ráthaithe teachtaireachta idir dhá nód, caithfidh sé a bheith in ann nasc a bhunú leis an taobh eile. Chun nasc a bhunú, seolann an seoltóir paicéad le bratach FirstPacket, rud a chiallóidh an freagra go mbunaítear an nasc. Socraíonn gach paicéad freagartha, nó, i bhfocail eile, paicéid admhála, luach réimse PacketNumber go ceann amháin níos mó i gcónaí ná an luach PacketNumber is mó de na paicéid a fuarthas go rathúil. Is é an réimse Roghanna don chéad phaicéad a seoladh ná méid na teachtaireachta.

Úsáidtear meicníocht den chineál céanna chun nasc a fhoirceannadh. Tá an bhratach LastPacket socraithe ar an bpaicéad deireanach den teachtaireacht. Sa phaicéad freagartha, léirítear uimhir an phaicéad deiridh + 1, rud a chiallaíonn go n-éireoidh le seachadadh na teachtaireachta don taobh glactha.
Léaráid maidir le bunú ceangail agus foirceannadh:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Nuair a bhunaítear an nasc, tosaíonn aistriú sonraí. Tarchuirtear sonraí i mbloic paicéid. Tá líon seasta paicéid i ngach bloc, seachas an ceann deireanach. Tá sé comhionann le méid na fuinneoige glactha/tarchurtha. Seans go mbeidh níos lú paicéid sa bhloc deireanach sonraí. Tar éis gach bloc a sheoladh, fanann an taobh seolta le deimhniú seachadta nó le hiarratas chun paicéid caillte a athsheachadadh, rud a fhágann an fhuinneog glactha/tarchurtha ar oscailt chun freagraí a fháil. Tar éis dó an seachadadh bloc a dhearbhú, aistrítear an fhuinneog glactha/tarchurtha agus seoltar an chéad bhloc eile sonraí.

Faigheann an taobh glactha na paicéid. Déantar gach paicéad a sheiceáil féachaint an dtagann sé laistigh den fhuinneog tarchuir. Déantar paicéid agus dúblaigh nach dtagann isteach san fhuinneog a scagadh amach. Mar Má tá méid na fuinneoige socraithe agus mar an gcéanna don fhaighteoir agus don seoltóir, ansin i gcás bloc paicéid a sheachadadh gan aon chaillteanas, aistrítear an fhuinneog chun paicéid den chéad bhloc eile sonraí a fháil agus tá deimhniú seachadta seolta. Mura líonann an fhuinneog laistigh den tréimhse a shocraíonn an t-amadóir oibre, cuirfear tús le seiceáil ar na paicéid nár seachadadh agus seolfar iarratais ar athsheachadadh.
Léaráid Atharchuir:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Teorainneacha ama agus amadóirí prótacail

Tá go leor cúiseanna ann nach féidir nasc a bhunú. Mar shampla, má tá an páirtí glactha as líne. Sa chás seo, agus iarracht á déanamh nasc a bhunú, dúnfar an nasc faoin teorainn ama. Úsáideann cur i bhfeidhm Iontaofa an UDP dhá amadóir chun amanna a shocrú. Úsáidtear an chéad cheann, an t-amadóir oibre, chun fanacht le freagra ón gcianóstach. Má lasann sé ar thaobh an tseoltóra, ansin tá an paicéad is déanaí seolta dochloíte. Má théann an t-amadóir in éag ag an bhfaighteoir, déantar seiceáil ar phaicéid caillte agus seoltar iarratais ar athsheachadadh.

Is gá an dara lasc ama chun an nasc a dhúnadh i gcás easpa cumarsáide idir na nóid. Ar thaobh an tseoltóra, tosaíonn sé díreach tar éis don lasc ama oibre dul in éag, agus fanann sé ar fhreagra ón nód iargúlta. Mura bhfuil aon fhreagra laistigh den tréimhse shonraithe, cuirtear deireadh leis an nasc agus scaoiltear acmhainní. Maidir leis an taobh glactha, cuirtear tús leis an lasc ama dúnta tar éis don lasc ama oibre dul in éag faoi dhó. Tá sé seo riachtanach chun árachas i gcoinne chailliúint an phacáid deimhnithe. Nuair a théann an t-amadóir in éag, cuirtear deireadh leis an nasc freisin agus scaoiltear acmhainní.

Léaráid iontaofa staid tarchurtha UDP

Cuirtear prionsabail an phrótacail i bhfeidhm i meaisín stát críochta, agus tá gach stát freagrach as loighic áirithe próiseála paicéid.
Léaráid Stáit UDP iontaofa:

Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Dúnta - ní stát é i ndáiríre, is pointe tosaigh agus pointe deiridh é don uathoibritheoir. Don stát Dúnta faightear bloc rialaithe tarchuir, a chuireann freastalaí UDP asincrónach i bhfeidhm, a chuireann paicéid ar aghaidh chuig na naisc chuí agus a chuireann tús le próiseáil stáit.

FirstPacketSending – an staid tosaigh ina bhfuil an nasc amach nuair a sheoltar an teachtaireacht.

Sa stát seo, seoltar an chéad phaicéad le haghaidh gnáth-theachtaireachtaí. I gcás teachtaireachtaí gan deimhniú seolta, is é seo an t-aon stát ina seoltar an teachtaireacht iomlán.

SendingCycle – an bunstaid maidir le tarchur paicéid teachtaireachta.

Aistriú chuige ón stát FirstPacketSending déanta tar éis an chéad phaicéad den teachtaireacht a bheith seolta. Is sa riocht seo a thagann gach admháil agus iarratas ar ath-tarchur. Is féidir imeacht uaidh in dhá chás - i gcás seachadadh rathúil na teachtaireachta nó trí am istigh.

Fuarthas FirstPacket – staid tosaigh fhaighteoir na teachtaireachta.

Seiceálann sé cruinneas thús an tarchuir, cruthaíonn sé na struchtúir riachtanacha, agus cuireann sé admháil go bhfuarthas an chéad phaicéad.

Maidir le teachtaireacht atá comhdhéanta de phaicéad amháin agus a seoladh gan úsáid a bhaint as cruthúnas seachadta, is é seo an t-aon stát. Tar éis teachtaireacht den sórt sin a phróiseáil, tá an nasc dúnta.

Ag cruinniú – an bunstaid chun paicéid teachtaireachtaí a fháil.

Scríobhann sé paicéid chuig stóráil shealadach, seiceann sé le haghaidh caillteanas paicéid, seolann sé admhálacha as bloc paicéid agus an teachtaireacht iomlán a sheachadadh, agus cuireann sé iarratais ar athsheachadadh paicéid caillte. I gcás go n-éireoidh leis an teachtaireacht iomlán a fháil, téann an nasc isteach sa stát Críochnaithe, ar shlí eile, fágtar teorainn ama.

Críochnaithe – an nasc a dhúnadh i gcás go n-éireoidh leis an teachtaireacht iomlán a fháil.

Tá an staid seo riachtanach do thionól na teachtaireachta agus don chás nuair a cailleadh dearbhú seachadta na teachtaireachta ar an mbealach chuig an seoltóir. Tá teorainn ama fágtha as an staid seo, ach meastar go bhfuil an nasc dúnta go rathúil.

Níos doimhne isteach an cód. aonad rialaithe tarchurtha

Is é ceann de na príomhghnéithe de UDP Iontaofa an bloc rialaithe tarchurtha. Is é tasc an bhloc seo ná naisc reatha agus eilimintí cúnta a stóráil, paicéid ag teacht isteach a dháileadh ar na naisc chomhfhreagracha, comhéadan a sholáthar chun paicéid a sheoladh chuig nasc, agus an API prótacal a chur i bhfeidhm. Faigheann an bloc rialaithe tarchurtha paicéid ón gciseal UDP agus cuireann sé ar aghaidh iad chuig an meaisín stáit le haghaidh próiseála. Chun paicéid a fháil, cuireann sé freastalaí UDP asincrónach i bhfeidhm.
Roinnt ball den rang 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;    	
  //...
}

Freastalaí UDP asincrónach a chur i bhfeidhm:

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

I gcás gach aistrithe teachtaireachta, cruthaítear struchtúr ina bhfuil faisnéis faoin nasc. Tugtar struchtúr den sórt sin taifead ceangail.
Roinnt ball den rang 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;
  //...
}

Níos doimhne isteach an cód. stáit

Cuireann na Stáit meaisín stáit an phrótacail UDP iontaofa i bhfeidhm, áit a ndéantar príomhphróiseáil na bpacáistí. Soláthraíonn an rang teibí ReliableUdpState comhéadan don stát:

Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Cuirtear loighic iomlán an phrótacail i bhfeidhm ag na ranganna a chuirtear i láthair thuas, mar aon le haicme cúnta a sholáthraíonn modhanna statacha, mar shampla, mar shampla, an ceanntásc ReliableUdp a thógáil ón taifead nasc.

Ansin, déanfaimid breithniú mionsonraithe ar chur i bhfeidhm na modhanna comhéadan a chinneann halgartaim bhunúsacha an phrótacail.

Modh DisposeByTimeout

Tá an modh DisposeByTimeout freagrach as acmhainní ceangail a scaoileadh tar éis teorainn ama agus as seachadadh teachtaireachta rathúil/neamhrathúil a chur in iúl.
ReliableUdpState.DisposeByTime Out:

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

Níl sé sáraithe ach sa stát Críochnaithe.
Críochnaithe.Dispose ByTime Out:

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

Modh ProcessPackets

Tá an modh ProcessPackets freagrach as próiseáil bhreise a dhéanamh ar phacáiste nó ar phacáistí. Glaoitear air go díreach nó trí lasc ama feithimh paicéad.

In ann Ag cruinniú tá an modh sáraithe agus tá sé freagrach as seiceáil le haghaidh paicéid caillte agus aistriú chuig an stát Críochnaithe, i gcás an paicéad deireanach a fháil agus seiceáil rathúil a rith
Cóimeáil.Pacáistí Próisis:

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

In ann SendingCycle ní thugtar an modh seo ach ar an lasc ama, agus tá sé freagrach as an teachtaireacht dheireanach a athsheoladh, chomh maith leis an lasc ama gar don nasc a chumasú.
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);
}

In ann Críochnaithe stopann an modh an t-amadóir reatha agus cuireann sé an teachtaireacht chuig na síntiúsóirí.
Críochnaithe.Pacáistí Próisis:

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

Modh ReceivePacket

In ann Fuarthas FirstPacket is é príomhthasc an mhodha ná a chinneadh an bhfuil an chéad phaicéad teachtaireachta tagtha ar an gcomhéadan i ndáiríre, agus freisin teachtaireacht a bhailiú ina bhfuil paicéad amháin.
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);
  }
}

In ann SendingCycle sáraítear an modh seo chun glacadh le hadmhálacha seachadta agus iarratais ar atarchur.
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));
}

In ann Ag cruinniú sa mhodh ReceivePacket, déantar an phríomhobair maidir le teachtaireacht a chur le chéile ó phaicéid atá ag teacht isteach.
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);
  }
}

In ann Críochnaithe is é an t-aon tasc a bhaineann leis an modh ná ath-admháil a sheoladh maidir le seachadadh rathúil na teachtaireachta.
Críochnaithe.ReceivePacket:

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

Seol Modh Paicéad

In ann FirstPacketSending seolann an modh seo an chéad phaicéad sonraí, nó, mura dteastaíonn deimhniú seachadta ón teachtaireacht, an teachtaireacht iomlán.
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);
}

In ann SendingCycle ar an modh seo, seoltar bloc paicéid.
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 );
  }
}

Níos doimhne isteach an cód. Naisc a Chruthú agus a Bhunú

Anois go bhfuil na bunstáit agus na modhanna a úsáidtear chun stáit a láimhseáil feicthe againn, déanaimis roinnt samplaí a bhriseadh síos ar conas a oibríonn an prótacal ar bhealach níos mine.
Léaráid tarchuir sonraí faoi ghnáthchoinníollacha:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Smaoinigh go mion ar an gcruthú taifead ceangail chun an chéad phaicéad a nascadh agus a sheoladh. Cuirtear tús leis an aistriú i gcónaí ag an bhfeidhmchlár a ghlaonn an API Seol teachtaireacht. Ansin, déantar modh StartTransmission an bhloc rialaithe tarchurtha a agairt, a chuireann tús le tarchur sonraí don teachtaireacht nua.
Ag cruthú nasc amach:

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

An chéad phaicéad a sheoladh (staid 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);
}

Tar éis an chéad phaicéad a sheoladh, téann an seoltóir isteach sa stát SendingCycle – fan go ndearbhaítear seachadadh an phacáiste.
Faigheann an taobh glactha, ag baint úsáide as an modh EndReceive, an paicéad seolta, cruthaítear ceann nua taifead ceangail agus cuireann sé an paicéad seo, le ceanntásc réamhphasáilte, chuig modh ReceivePacket an stáit lena phróiseáil Fuarthas FirstPacket
Ag cruthú nasc ar an taobh glactha:

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

An chéad phaicéad a fháil agus admháil a sheoladh (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);
  }
}

Níos doimhne isteach an cód. An nasc a dhúnadh ar an am istigh

Is cuid thábhachtach den UDP Iontaofa é láimhseáil am istigh. Smaoinigh ar shampla inar theip ar nód idirmheánach agus nárbh fhéidir sonraí a sheachadadh sa dá threo.
Léaráid chun nasc a dhúnadh faoin am istigh:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Mar is léir ón léaráid, tosaíonn lasc ama oibre an tseoltóra díreach tar éis bloc paicéid a sheoladh. Tarlaíonn sé seo i modh SendPacket an stáit SendingCycle.
An t-amadóir oibre a chumasú (staid SendCycle):

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

Socraítear na tréimhsí ama nuair a chruthaítear an nasc. Is é 5 soicind an ShortTimerPeriod réamhshocraithe. Sa sampla, tá sé socraithe go dtí 1,5 soicind.

Le haghaidh nasc isteach, tosaíonn an t-amadóir tar éis an paicéad sonraí deireanach a thagann isteach a fháil, tarlaíonn sé seo i modh ReceivePacket an stáit Ag cruinniú
An t-amadóir oibre a chumasú (Stáit tionóil):

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

Níor tháinig a thuilleadh paicéid ar an nasc isteach agus tú ag fanacht leis an amadóir oibre. D'imigh an t-amadóir agus ghlaoigh sé ar an modh ProcessPackets, áit a bhfuarthas na paicéid caillte agus seoladh iarratais athsheachadta den chéad uair.
Iarratais athsheachadta a sheoladh (Stáit tionóil):

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

Tá an athróg TimerSecondTry socraithe go fíor. Tá an t-athróg seo freagrach as an t-amadóir oibre a atosú.

Ar thaobh an tseoltóra, spreagtar an t-amadóir oibre freisin agus déantar doicheall ar an bpaicéad a seoladh go deireanach.
Amadóir dúnta ceangail á chumasú (staid SendCycle):

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

Tar éis sin, tosaíonn an t-amadóir dúnta ceangail sa nasc amach.
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 é 30 soicind tréimhse am istigh an cheangail de réir réamhshocraithe.

Tar éis tamaill ghearr, lasann an t-amadóir oibre ar thaobh an fhaighteora arís, seoltar iarratais arís, agus ina dhiaidh sin tosaíonn an t-amadóir dúnta don nasc isteach

Nuair a théann na gar-amadóirí dóiteáin, scaoiltear acmhainní uile an dá thaifead nasc. Tuairiscíonn an seoltóir an teip seachadta chuig an bhfeidhmchlár in aghaidh an tsrutha (féach API UDP iontaofa).
Acmhainní taifid naisc á scaoileadh:

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

Níos doimhne isteach an cód. Aistriú sonraí a athbhunú

Léaráid aisghabhála tarchuir sonraí i gcás caillteanas paicéid:Cur i bhfeidhm an phrótacail Iontaofa Udp do .Net

Mar a pléadh cheana agus an nasc a dhúnadh le linn am istigh, nuair a rachaidh an t-amadóir oibre in éag, seiceálfaidh an glacadóir na paicéid caillte. I gcás caillteanas paicéad, tiomsófar liosta de líon na bpacáistí nár shroich an faighteoir. Cuirtear na huimhreacha seo isteach sa tsraith LostPackets de cheangail ar leith, agus seoltar iarratais ar athsheachadadh.
Iarratais a sheoladh ar phacáistí a athsheoladh (Stáit tionóil):

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

Glacfaidh an seoltóir leis an iarratas athsheachadta agus seolfaidh sé na paicéid atá in easnamh. Is fiú a thabhairt faoi deara go bhfuil an t-amadóir dúnta ceangail tosaithe cheana féin ag an seoltóir ag an nóiméad seo agus, nuair a fhaightear iarratas, déantar é a athshocrú.
Paicéid caillte a athsheoladh (staid SendCycle):

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

Faigheann an nasc isteach an paicéad athsheolta (paicéad #3 sa léaráid). Déantar seiceáil féachaint an bhfuil an fhuinneog glactha lán agus an bhfuil gnáth-tharchur sonraí curtha ar ais.
Ag seiceáil le haghaidh amas sa fhuinneog glactha (Stáit tionóil):

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 iontaofa

Chun idirghníomhú leis an bprótacal aistrithe sonraí, tá rang Udp iontaofa oscailte, atá ina fhillteán thar an mbloc rialaithe aistrithe. Seo iad na baill is tábhachtaí den rang:

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

Faightear teachtaireachtaí trí shíntiús. Síniú toscaire don mhodh aisghlao:

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

Teachtaireacht:

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

Chun síntiús a íoc le cineál teachtaireachta ar leith agus/nó seoltóir ar leith, úsáidtear dhá pharaiméadar roghnacha: ReliableUdpMessageTypes messageType agus IPEndPoint ipEndPoint.

Cineálacha teachtaireachta:

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

Seoltar an teachtaireacht go neamhshioncronach; chuige seo, cuireann an prótacal samhail ríomhchláraithe asincrónach i bhfeidhm:

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

Beidh an toradh ar theachtaireacht a sheoladh fíor - má éirigh leis an teachtaireacht an faighteoir a bhaint amach agus bréagach - má dúnadh an ceangal faoin am istigh:

public bool EndSendMessage(IAsyncResult asyncResult)

Conclúid

Níl mórán cur síos déanta san Airteagal seo. Meicníochtaí meaitseála snáithe, láimhseáil eisceachta agus earráide, cur i bhfeidhm modhanna seolta teachtaireachtaí asincrónacha. Ach croílár an phrótacail, ba cheart go mbeadh an cur síos ar an loighic maidir le paicéid a phróiseáil, nasc a bhunú, agus tréimhsí ama a láimhseáil, soiléir duit.

Tá an leagan léirithe den phrótacal seachadta iontaofa láidir agus solúbtha go leor chun na ceanglais a sainíodh roimhe seo a chomhlíonadh. Ach ba mhaith liom a chur leis gur féidir an cur i bhfeidhm a thuairiscítear a fheabhsú. Mar shampla, chun tréchur a mhéadú agus tréimhsí lasc ama a athrú go dinimiciúil, is féidir meicníochtaí cosúil le fuinneog sleamhnáin agus RTT a chur leis an bprótacal, beidh sé úsáideach freisin meicníocht a chur i bhfeidhm chun MTU a chinneadh idir nóid nasc (ach amháin má sheoltar teachtaireachtaí móra) .

Go raibh maith agat as d’aird, táim ag tnúth le do chuid tuairimí agus tuairimí.

PS Dóibh siúd a bhfuil suim acu sna sonraí nó díreach ag iarraidh an prótacal a thástáil, an nasc leis an tionscadal ar GitHube:
Tionscadal UDP iontaofa

Naisc úsáideacha agus earraí....

  1. Sonraíocht prótacail TCP: i mBéarla и sa Rúisis
  2. Sonraíocht prótacail UDP: i mBéarla и sa Rúisis
  3. Plé ar phrótacal RUDP: dréacht-ietf-sigtran-iontaofa-udp-00
  4. Prótacal Sonraí Iontaofa: RFC 908 и RFC 1151
  5. Cur i bhfeidhm simplí ar dheimhniú seachadta thar UDP: Tóg Rialú Iomlán Ar Do Líonrú Le .NET Agus UDP
  6. Airteagal a dhéanann cur síos ar shásraí trasnaithe NAT: Cumarsáid Piara-le-Piara ar fud Aistritheoirí Seoltaí Líonra
  7. Cur i bhfeidhm na samhla ríomhchláraithe asincrónach: Samhail Ríomhchlárúcháin Asioncrónach CLR a chur i bhfeidhm и Conas patrún dearaidh IAsyncResult a chur i bhfeidhm
  8. An tsamhail ríomhchlárúcháin asincrónach a aistriú chuig an bpatrún asincrónach tascbhunaithe (APM in TAP):
    TPL agus Traidisiúnta . GLAN Ríomhchlárú Asincrónach
    Idirphlé le Patrúin agus Cineálacha Asincrónacha Eile

Nuashonrú: Go raibh maith agat méara и sidristij don smaoineamh tasc a chur leis an gcomhéadan. Ní sháraítear comhoiriúnacht na leabharlainne le seanchórais oibriúcháin, mar gheall ar Tacaíonn an 4ú creat le freastalaí XP agus 2003 araon.

Foinse: will.com

Add a comment