Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Mae'r Rhyngrwyd wedi newid amser maith yn ôl. Un o brif brotocolau'r Rhyngrwyd - mae CDU yn cael ei ddefnyddio gan gymwysiadau nid yn unig i gyflwyno datagramau a darllediadau, ond hefyd i ddarparu cysylltiadau "cymheiriaid-i-gymar" rhwng nodau rhwydwaith. Oherwydd ei ddyluniad syml, mae gan y protocol hwn lawer o ddefnyddiau nas cynlluniwyd o'r blaen, fodd bynnag, nid yw diffygion y protocol, megis diffyg darpariaeth warantedig, wedi diflannu i unrhyw le. Mae'r erthygl hon yn disgrifio gweithrediad y protocol cyflawni gwarantedig dros y CDU.
Cynnwys:Mynediad
Gofynion Protocol
Pennawd CDU dibynadwy
Egwyddorion cyffredinol y protocol
Goramser ac amseryddion protocol
Diagram cyflwr trawsyrru CDU dibynadwy
Yn ddyfnach i mewn i'r cod. uned rheoli trawsyrru
Yn ddyfnach i mewn i'r cod. taleithiau

Yn ddyfnach i mewn i'r cod. Creu a Sefydlu Cysylltiadau
Yn ddyfnach i mewn i'r cod. Cau'r cysylltiad ar derfyn amser
Yn ddyfnach i mewn i'r cod. Adfer trosglwyddo data
API CDU dibynadwy
Casgliad
Dolenni ac erthyglau defnyddiol

Mynediad

Roedd pensaernïaeth wreiddiol y Rhyngrwyd yn rhagdybio gofod cyfeiriad homogenaidd lle roedd gan bob nod gyfeiriad IP byd-eang ac unigryw ac y gallent gyfathrebu'n uniongyrchol â nodau eraill. Nawr mae gan y Rhyngrwyd, mewn gwirionedd, bensaernïaeth wahanol - un maes o gyfeiriadau IP byd-eang a llawer o feysydd gyda chyfeiriadau preifat wedi'u cuddio y tu ôl i ddyfeisiau NAT.Yn y bensaernïaeth hon, dim ond dyfeisiau yn y gofod cyfeiriad byd-eang sy'n gallu cyfathrebu'n hawdd ag unrhyw un ar y rhwydwaith oherwydd bod ganddynt gyfeiriad IP unigryw y gellir ei lwybro'n fyd-eang. Gall nod ar rwydwaith preifat gysylltu â nodau eraill ar yr un rhwydwaith, a gall hefyd gysylltu â nodau adnabyddus eraill yn y gofod cyfeiriad byd-eang. Cyflawnir y rhyngweithiad hwn yn bennaf oherwydd y mecanwaith cyfieithu cyfeiriad rhwydwaith. Mae dyfeisiau NAT, fel llwybryddion Wi-Fi, yn creu cofnodion tabl cyfieithu arbennig ar gyfer cysylltiadau sy'n mynd allan ac yn addasu cyfeiriadau IP a rhifau porthladdoedd mewn pecynnau. Mae hyn yn caniatáu cysylltiadau sy'n mynd allan o'r rhwydwaith preifat i westeion yn y gofod cyfeiriad byd-eang. Ond ar yr un pryd, mae dyfeisiau NAT fel arfer yn rhwystro'r holl draffig sy'n dod i mewn oni bai bod rheolau ar wahân wedi'u gosod ar gyfer cysylltiadau sy'n dod i mewn.

Mae pensaernïaeth y Rhyngrwyd yn ddigon cywir ar gyfer cyfathrebu cleient-gweinydd, lle gall cleientiaid fod mewn rhwydweithiau preifat, ac mae gan weinyddion gyfeiriad byd-eang. Ond mae'n creu anawsterau ar gyfer cysylltiad uniongyrchol dau nod rhwng amrywiol rhwydweithiau preifat. Mae cysylltiad uniongyrchol rhwng dau nod yn bwysig ar gyfer cymwysiadau cyfoedion-i-gymar megis trosglwyddo llais (Skype), cael mynediad o bell i gyfrifiadur (TeamViewer), neu hapchwarae ar-lein.

Gelwir un o'r dulliau mwyaf effeithiol ar gyfer sefydlu cysylltiad cyfoedion-i-gymar rhwng dyfeisiau ar wahanol rwydweithiau preifat yn dyrnu twll. Defnyddir y dechneg hon yn fwyaf cyffredin gyda chymwysiadau yn seiliedig ar brotocol y CDU.

Ond os yw'ch cais angen cyflenwad gwarantedig o ddata, er enghraifft, rydych chi'n trosglwyddo ffeiliau rhwng cyfrifiaduron, yna bydd defnyddio CDU yn cael llawer o anawsterau oherwydd nad yw'r CDU yn brotocol dosbarthu gwarantedig ac nid yw'n darparu dosbarthiad pecynnau mewn trefn, yn wahanol i'r TCP protocol.

Yn yr achos hwn, er mwyn sicrhau cyflenwad pecyn gwarantedig, mae'n ofynnol gweithredu protocol haen cais sy'n darparu'r ymarferoldeb angenrheidiol ac yn gweithio dros y CDU.

Rwyf am nodi ar unwaith bod yna dechneg dyrnu twll TCP ar gyfer sefydlu cysylltiadau TCP rhwng nodau mewn gwahanol rwydweithiau preifat, ond oherwydd y diffyg cefnogaeth iddo gan lawer o ddyfeisiau NAT, nid yw fel arfer yn cael ei ystyried fel y brif ffordd i gysylltu. nodau o'r fath.

Am weddill yr erthygl hon, byddaf yn canolbwyntio ar weithredu'r protocol cyflawni gwarantedig yn unig. Disgrifir gweithrediad techneg dyrnu twll y CDU yn yr erthyglau canlynol.

Gofynion Protocol

  1. Dosbarthu pecynnau dibynadwy yn cael eu gweithredu trwy fecanwaith adborth cadarnhaol (cydnabyddiaeth gadarnhaol fel y'i gelwir)
  2. Yr angen i drosglwyddo data mawr yn effeithlon, h.y. rhaid i'r protocol osgoi cyfnewid pecynnau yn ddiangen
  3. Dylai fod yn bosibl canslo'r mecanwaith cadarnhau cyflenwad (y gallu i weithredu fel protocol CDU "pur")
  4. Y gallu i weithredu modd gorchymyn, gyda chadarnhad o bob neges
  5. Rhaid i'r uned sylfaenol o drosglwyddo data dros y protocol fod yn neges

Mae’r gofynion hyn yn cyd-fynd i raddau helaeth â’r gofynion Protocol Data Dibynadwy a ddisgrifir yn rfc908 и rfc1151, a dibynnais ar y safonau hynny wrth ddatblygu’r protocol hwn.

Er mwyn deall y gofynion hyn, gadewch i ni edrych ar amseriad trosglwyddo data rhwng dau nod rhwydwaith gan ddefnyddio'r protocolau TCP a CDU. Gadewch yn y ddau achos byddwn yn colli un pecyn.
Trosglwyddo data anrhyngweithiol dros TCP:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Fel y gallwch weld o'r diagram, rhag ofn y bydd pecyn yn cael ei golli, bydd TCP yn canfod y pecyn coll ac yn ei adrodd i'r anfonwr trwy ofyn am rif y segment coll.
Trosglwyddo data trwy brotocol CDU:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Nid yw CDU yn cymryd unrhyw gamau canfod colled. Cyfrifoldeb y cais yn gyfan gwbl yw rheoli gwallau trosglwyddo ym mhrotocol y CDU.

Cyflawnir canfod gwallau yn y protocol TCP trwy sefydlu cysylltiad â nod diwedd, storio cyflwr y cysylltiad hwnnw, nodi nifer y beitau a anfonwyd ym mhennyn pob pecyn, a hysbysu derbynebau gan ddefnyddio rhif cydnabod.

Yn ogystal, i wella perfformiad (h.y. anfon mwy nag un segment heb dderbyn cydnabyddiaeth), mae'r protocol TCP yn defnyddio'r ffenestr drosglwyddo fel y'i gelwir - nifer y beit o ddata y mae anfonwr y segment yn disgwyl ei dderbyn.

Am ragor o wybodaeth am y protocol TCP, gweler rfc793, o CDU i rfc768lle, mewn gwirionedd, maent yn cael eu diffinio.

O'r uchod, mae'n amlwg, er mwyn creu protocol cyflwyno neges dibynadwy dros y CDU (y cyfeirir ati yma wedi hyn fel CDU dibynadwy), mae'n ofynnol gweithredu mecanweithiau trosglwyddo data tebyg i TCP. sef:

  • arbed cyflwr cysylltiad
  • defnyddio rhifo segmentau
  • defnyddio pecynnau cadarnhau arbennig
  • defnyddio mecanwaith ffenestru symlach i gynyddu trwygyrch protocol

Yn ogystal, mae angen:

  • arwydd o ddechrau neges, i ddyrannu adnoddau ar gyfer y cysylltiad
  • arwydd diwedd neges, i drosglwyddo'r neges a dderbyniwyd i'r cais i fyny'r afon a rhyddhau adnoddau protocol
  • caniatáu i'r protocol cysylltiad-benodol analluogi'r mecanwaith cadarnhau danfoniad i weithredu fel CDU "pur".

Pennawd CDU dibynadwy

Dwyn i gof bod datagram CDU wedi'i amgáu mewn datagram IP. Mae'r pecyn CDU Dibynadwy wedi'i "lapio" yn briodol mewn datagram CDU.
Amgáu pennawd CDU dibynadwy:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Mae strwythur pennawd y CDU Dibynadwy yn eithaf syml:

Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

  • Baneri - baneri rheoli pecyn
  • MessageType - math o neges a ddefnyddir gan gymwysiadau i fyny'r afon i danysgrifio i negeseuon penodol
  • TransmissionId - mae rhif y trosglwyddiad, ynghyd â chyfeiriad a phorthladd y derbynnydd, yn nodi'r cysylltiad yn unigryw
  • PacketNumber - rhif pecyn
  • Opsiynau - opsiynau protocol ychwanegol. Yn achos y pecyn cyntaf, fe'i defnyddir i nodi maint y neges

Mae'r baneri fel a ganlyn:

  • FirstPacket - pecyn cyntaf y neges
  • NoAsk - nid oes angen mecanwaith cydnabod i alluogi'r neges
  • LastPacket - pecyn olaf y neges
  • RequestForPacket - pecyn cadarnhau neu gais am becyn coll

Egwyddorion cyffredinol y protocol

Gan fod CDU Dibynadwy yn canolbwyntio ar drosglwyddo neges gwarantedig rhwng dau nod, rhaid iddo allu sefydlu cysylltiad â'r ochr arall. Er mwyn sefydlu cysylltiad, mae'r anfonwr yn anfon pecyn gyda baner FirstPacket, a bydd yr ymateb iddo yn golygu bod y cysylltiad wedi'i sefydlu. Mae pob pecyn ymateb, neu, mewn geiriau eraill, becynnau cydnabyddiaeth, bob amser yn gosod gwerth y maes PacketNumber i un yn fwy na gwerth PacketNumber mwyaf y pecynnau a dderbyniwyd yn llwyddiannus. Maint y neges yw'r maes Dewisiadau ar gyfer y pecyn cyntaf a anfonwyd.

Defnyddir mecanwaith tebyg i derfynu cysylltiad. Mae baner LastPacket wedi'i gosod ar becyn olaf y neges. Yn y pecyn ymateb, nodir rhif y pecyn olaf + 1, sydd ar gyfer yr ochr dderbyn yn golygu cyflwyno'r neges yn llwyddiannus.
Diagram sefydlu cysylltiad a therfynu:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Pan fydd y cysylltiad wedi'i sefydlu, mae trosglwyddo data yn dechrau. Trosglwyddir data mewn blociau o becynnau. Mae pob bloc, ac eithrio'r un olaf, yn cynnwys nifer sefydlog o becynnau. Mae'n hafal i faint y ffenestr derbyn / trosglwyddo. Efallai y bydd gan y bloc olaf o ddata lai o becynnau. Ar ôl anfon pob bloc, mae'r ochr anfon yn aros am gadarnhad danfoniad neu gais i ail-ddanfon pecynnau coll, gan adael y ffenestr derbyn / trosglwyddo ar agor i dderbyn ymatebion. Ar ôl derbyn cadarnhad o ddanfoniad bloc, mae'r ffenestr derbyn / trosglwyddo yn symud ac anfonir y bloc nesaf o ddata.

Mae'r ochr dderbyn yn derbyn y pecynnau. Mae pob pecyn yn cael ei wirio i weld a yw'n dod o fewn y ffenestr drosglwyddo. Mae pecynnau a chopïau dyblyg nad ydynt yn syrthio i'r ffenestr yn cael eu hidlo allan. Achos Os yw maint y ffenestr yn sefydlog a'r un peth ar gyfer y derbynnydd a'r anfonwr, yna yn achos bloc o becynnau'n cael eu danfon heb eu colli, mae'r ffenestr yn cael ei symud i dderbyn pecynnau o'r bloc nesaf o ddata a chadarnhad danfon yw anfonwyd. Os na fydd y ffenestr yn llenwi o fewn y cyfnod a osodwyd gan yr amserydd gwaith, yna dechreuir siec ar ba becynnau sydd heb eu dosbarthu ac anfonir ceisiadau am ailddosbarthu.
Diagram ail-drosglwyddo:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Goramser ac amseryddion protocol

Mae yna nifer o resymau pam na ellir sefydlu cysylltiad. Er enghraifft, os yw'r parti derbyn all-lein. Yn yr achos hwn, wrth geisio sefydlu cysylltiad, bydd y cysylltiad yn cael ei gau erbyn terfyn amser. Mae gweithrediad CDU Dibynadwy yn defnyddio dau amserydd i osod terfynau amser. Defnyddir y cyntaf, yr amserydd gweithio, i aros am ymateb gan y gwesteiwr o bell. Os yw'n tanio ar ochr yr anfonwr, yna mae'r pecyn a anfonwyd ddiwethaf yn ddrwg. Os yw'r amserydd yn dod i ben wrth y derbynnydd, yna cynhelir siec am becynnau coll ac anfonir ceisiadau am ailddosbarthu.

Mae angen yr ail amserydd i gau'r cysylltiad rhag ofn y bydd diffyg cyfathrebu rhwng y nodau. Ar gyfer ochr yr anfonwr, mae'n cychwyn yn syth ar ôl i'r amserydd gweithio ddod i ben, ac yn aros am ymateb gan y nod anghysbell. Os na cheir ymateb o fewn y cyfnod penodedig, terfynir y cysylltiad a rhyddheir adnoddau. Ar gyfer yr ochr dderbyn, mae'r amserydd cau cysylltiad yn cael ei gychwyn ar ôl i'r amserydd gwaith ddod i ben ddwywaith. Mae hyn yn angenrheidiol i yswirio rhag colli'r pecyn cadarnhau. Pan ddaw'r amserydd i ben, mae'r cysylltiad hefyd yn cael ei derfynu a chaiff adnoddau eu rhyddhau.

Diagram cyflwr trawsyrru CDU dibynadwy

Mae egwyddorion y protocol yn cael eu gweithredu mewn peiriant cyflwr cyfyngedig, y mae pob cyflwr yn gyfrifol am resymeg benodol o brosesu pecynnau.
Diagram Talaith CDU dibynadwy:

Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Ar gau - nid yw'n gyflwr mewn gwirionedd, mae'n fan cychwyn a diwedd ar gyfer yr awtomaton. Ar gyfer y wladwriaeth Ar gau derbynnir bloc rheoli trawsyrru, sydd, wrth weithredu gweinydd CDU asyncronig, yn anfon pecynnau ymlaen at y cysylltiadau priodol ac yn dechrau prosesu cyflwr.

Anfon Pecyn Cyntaf – y cyflwr cychwynnol y mae'r cysylltiad sy'n mynd allan ynddo pan anfonir y neges.

Yn y cyflwr hwn, anfonir y pecyn cyntaf ar gyfer negeseuon arferol. Ar gyfer negeseuon heb anfon cadarnhad, dyma'r unig gyflwr lle mae'r neges gyfan yn cael ei hanfon.

SendingCycl – cyflwr sylfaenol ar gyfer trosglwyddo pecynnau neges.

Pontio iddo o'r wladwriaeth Anfon Pecyn Cyntaf yn cael ei wneud ar ôl i becyn cyntaf y neges gael ei anfon. Yn y cyflwr hwn y daw pob cydnabyddiaeth a chais am aildrosglwyddiadau. Mae'n bosibl gadael mewn dau achos - rhag ofn y bydd y neges yn cael ei chyflwyno'n llwyddiannus neu erbyn terfyn amser.

Derbyniwyd Packet Cyntaf – cyflwr cychwynnol derbynnydd y neges.

Mae'n gwirio cywirdeb dechrau'r trosglwyddiad, yn creu'r strwythurau angenrheidiol, ac yn anfon cydnabyddiaeth o dderbyn y pecyn cyntaf.

Ar gyfer neges sy'n cynnwys un pecyn ac a anfonwyd heb ddefnyddio prawf danfon, dyma'r unig gyflwr. Ar ôl prosesu neges o'r fath, mae'r cysylltiad ar gau.

Cydosod - cyflwr sylfaenol ar gyfer derbyn pecynnau neges.

Mae'n ysgrifennu pecynnau i storfa dros dro, yn gwirio am golli pecynnau, yn anfon cydnabyddiaeth am ddosbarthu bloc o becynnau a'r neges gyfan, ac yn anfon ceisiadau am ailddosbarthu pecynnau coll. Mewn achos o dderbyn y neges gyfan yn llwyddiannus, mae'r cysylltiad yn mynd i'r wladwriaeth Cwblhawyd, fel arall, mae terfyn amser yn dod i ben.

Cwblhawyd - cau'r cysylltiad rhag ofn y bydd y neges gyfan yn cael ei derbyn yn llwyddiannus.

Mae'r cyflwr hwn yn angenrheidiol ar gyfer cydosod y neges ac ar gyfer yr achos pan gollwyd cadarnhad danfon y neges ar y ffordd i'r anfonwr. Mae terfyn amser yn gadael y cyflwr hwn, ond ystyrir bod y cysylltiad wedi'i gau'n llwyddiannus.

Yn ddyfnach i mewn i'r cod. uned rheoli trawsyrru

Un o elfennau allweddol CDU Dibynadwy yw'r bloc rheoli trosglwyddo. Tasg y bloc hwn yw storio cysylltiadau cyfredol ac elfennau ategol, dosbarthu pecynnau sy'n dod i mewn i'r cysylltiadau cyfatebol, darparu rhyngwyneb ar gyfer anfon pecynnau i gysylltiad, a gweithredu'r API protocol. Mae'r bloc rheoli trosglwyddo yn derbyn pecynnau o'r haen CDU ac yn eu hanfon ymlaen i'r peiriant cyflwr i'w prosesu. I dderbyn pecynnau, mae'n gweithredu gweinydd CDU asyncronaidd.
Rhai aelodau o'r dosbarth 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;    	
  //...
}

Gweithredu gweinydd CDU asyncronaidd:

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

Ar gyfer pob trosglwyddiad neges, crëir strwythur sy'n cynnwys gwybodaeth am y cysylltiad. Gelwir strwythur o'r fath cofnod cysylltiad.
Rhai aelodau o'r dosbarth 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;
  //...
}

Yn ddyfnach i mewn i'r cod. taleithiau

Mae gwladwriaethau'n gweithredu peiriant cyflwr y protocol CDU Dibynadwy, lle mae prif brosesu pecynnau yn digwydd. Mae'r dosbarth haniaethol ReliableUdpState yn darparu rhyngwyneb ar gyfer y wladwriaeth:

Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Gweithredir rhesymeg gyfan y protocol gan y dosbarthiadau a gyflwynir uchod, ynghyd â dosbarth ategol sy'n darparu dulliau statig, megis, er enghraifft, adeiladu'r pennawd ReliableUdp o'r cofnod cysylltiad.

Nesaf, byddwn yn ystyried yn fanwl weithrediad y dulliau rhyngwyneb sy'n pennu algorithmau sylfaenol y protocol.

dull DisposeByTimeout

Mae'r dull DisposeByTimeout yn gyfrifol am ryddhau adnoddau cysylltu ar ôl terfyn amser ac am roi arwydd o neges lwyddiannus/aflwyddiannus.
ReliableUdpState.DisposeByTimeout:

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

Dim ond yn y wladwriaeth y caiff ei ddiystyru Cwblhawyd.
Wedi'i Gwblhau.DisposeByTimeout:

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

Dull ProsesPackets

Mae'r dull ProcessPackets yn gyfrifol am brosesu pecyn neu becynnau ychwanegol. Wedi'i alw'n uniongyrchol neu drwy amserydd aros pecyn.

Galluog Cydosod mae'r dull yn cael ei ddiystyru ac mae'n gyfrifol am wirio am becynnau coll a throsglwyddo i'r wladwriaeth Cwblhawyd, rhag ofn derbyn y pecyn olaf a phasio siec lwyddiannus
Cydosod.Pacedi Proses:

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

Galluog SendingCycl dim ond ar amserydd y gelwir y dull hwn, ac mae'n gyfrifol am ail-anfon y neges olaf, yn ogystal â galluogi'r amserydd cau cysylltiad.
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);
}

Galluog Cwblhawyd mae'r dull yn atal yr amserydd rhedeg ac yn anfon y neges at y tanysgrifwyr.
Completed.ProcessPackets:

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

Dull DerbynPacket

Galluog Derbyniwyd Packet Cyntaf prif dasg y dull yw penderfynu a gyrhaeddodd y pecyn neges gyntaf y rhyngwyneb mewn gwirionedd, a hefyd i gasglu neges sy'n cynnwys un pecyn.
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);
  }
}

Galluog SendingCycl mae'r dull hwn yn cael ei ddiystyru i dderbyn cydnabyddiaethau danfon a cheisiadau ailddarlledu.
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));
}

Galluog Cydosod yn y dull ReceivePacket, mae'r prif waith o gydosod neges o becynnau sy'n dod i mewn yn digwydd.
Cydosod.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);
  }
}

Galluog Cwblhawyd unig orchwyl y dull yw anfon ail gydnabod bod y neges wedi'i throsglwyddo'n llwyddiannus.
Wedi'i gwblhau.ReceivePacket:

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

Anfon Dull Pecyn

Galluog Anfon Pecyn Cyntaf mae'r dull hwn yn anfon y pecyn data cyntaf, neu, os nad oes angen cadarnhad danfoniad ar y neges, y neges gyfan.
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);
}

Galluog SendingCycl yn y dull hwn, anfonir bloc o becynnau.
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 );
  }
}

Yn ddyfnach i mewn i'r cod. Creu a Sefydlu Cysylltiadau

Nawr ein bod wedi gweld y cyflyrau sylfaenol a'r dulliau a ddefnyddir i drin gwladwriaethau, gadewch i ni ddadansoddi ychydig o enghreifftiau o sut mae'r protocol yn gweithio ychydig yn fwy manwl.
Diagram trosglwyddo data o dan amodau arferol:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Ystyriwch yn fanwl y greadigaeth cofnod cysylltiad i gysylltu ac anfon y pecyn cyntaf. Mae'r trosglwyddiad bob amser yn cael ei gychwyn gan y rhaglen sy'n galw'r API anfon neges. Nesaf, defnyddir dull StartTransmission y bloc rheoli trosglwyddo, sy'n dechrau trosglwyddo data ar gyfer y neges newydd.
Creu cysylltiad sy'n mynd allan:

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

Anfon y pecyn cyntaf (cyflwr 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);
}

Ar ôl anfon y pecyn cyntaf, mae'r anfonwr yn mynd i mewn i'r wladwriaeth SendingCycl - aros am gadarnhad o ddanfon pecyn.
Mae'r ochr dderbyn, gan ddefnyddio'r dull EndReceive, yn derbyn y pecyn a anfonwyd, yn creu pecyn newydd cofnod cysylltiad ac yn trosglwyddo'r pecyn hwn, gyda phennawd wedi'i ddosrannu ymlaen llaw, i ddull ReceivePacket y wladwriaeth i'w brosesu Derbyniwyd Packet Cyntaf
Creu cysylltiad ar yr ochr dderbyn:

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

Derbyn y pecyn cyntaf ac anfon cydnabyddiaeth (cyflwr 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);
  }
}

Yn ddyfnach i mewn i'r cod. Cau'r cysylltiad ar derfyn amser

Mae ymdrin â goramser yn rhan bwysig o'r CDU Dibynadwy. Ystyriwch enghraifft lle methodd nod canolradd a daeth yn amhosibl darparu data i'r ddau gyfeiriad.
Diagram ar gyfer cau cysylltiad erbyn terfyn amser:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Fel y gwelir o'r diagram, mae amserydd gwaith yr anfonwr yn cychwyn yn syth ar ôl anfon bloc o becynnau. Mae hyn yn digwydd yn y dull SenPacket y wladwriaeth SendingCycl.
Galluogi'r amserydd gwaith (cyflwr SendingCycle):

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

Mae'r cyfnodau amserydd yn cael eu gosod pan fydd y cysylltiad yn cael ei greu. Y ShortTimerPeriod rhagosodedig yw 5 eiliad. Yn yr enghraifft, mae wedi'i osod i 1,5 eiliad.

Ar gyfer cysylltiad sy'n dod i mewn, mae'r amserydd yn dechrau ar ôl derbyn y pecyn data olaf sy'n dod i mewn, mae hyn yn digwydd yn null ReceivePacket y wladwriaeth Cydosod
Galluogi'r amserydd gwaith (cyflwr cydosod):

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

Ni chyrhaeddodd mwy o becynnau ar y cysylltiad sy'n dod i mewn wrth aros am yr amserydd gweithio. Aeth yr amserydd i ffwrdd a galw'r dull ProcessPackets, lle darganfuwyd y pecynnau coll ac anfonwyd ceisiadau ailddosbarthu am y tro cyntaf.
Anfon ceisiadau ailddosbarthu (cyflwr cydosod):

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

Mae'r newidyn TimerSecondTry wedi'i osod i yn wir. Mae'r newidyn hwn yn gyfrifol am ailgychwyn yr amserydd gweithio.

Ar ochr yr anfonwr, mae'r amserydd gweithio hefyd yn cael ei sbarduno ac mae'r pecyn a anfonwyd ddiwethaf yn cael ei ddigio.
Galluogi amserydd cau cysylltiad (cyflwr SendingCycle):

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

Ar ôl hynny, mae'r amserydd cau cysylltiad yn cychwyn yn y cysylltiad sy'n mynd allan.
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);
}

Cyfnod terfyn amser cau cysylltiad yw 30 eiliad yn ddiofyn.

Ar ôl cyfnod byr, mae'r amserydd gweithio ar ochr y derbynnydd yn tanio eto, anfonir ceisiadau eto, ac ar ôl hynny mae'r amserydd cau cysylltiad yn dechrau ar gyfer y cysylltiad sy'n dod i mewn

Pan fydd yr amseryddion agos yn tanio, rhyddheir holl adnoddau'r ddau gofnod cysylltiad. Mae'r anfonwr yn adrodd am fethiant danfon i'r rhaglen i fyny'r afon (gweler API CDU Dibynadwy).
Rhyddhau adnoddau cofnod cysylltiad:

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

Yn ddyfnach i mewn i'r cod. Adfer trosglwyddo data

Diagram adfer trosglwyddo data rhag ofn y bydd pecyn yn cael ei golli:Gweithredu'r protocol CDU Dibynadwy ar gyfer .Net

Fel y trafodwyd eisoes wrth gau'r cysylltiad ar derfyn amser, pan ddaw'r amserydd gweithio i ben, bydd y derbynnydd yn gwirio am becynnau coll. Mewn achos o golli pecyn, bydd rhestr o'r nifer o becynnau na chyrhaeddodd y derbynnydd yn cael ei llunio. Rhoddir y rhifau hyn i mewn i gyfres LostPackets o gysylltiad penodol, ac anfonir ceisiadau am ailddosbarthiad.
Anfon ceisiadau i ailddosbarthu pecynnau (cyflwr cydosod):

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

Bydd yr anfonwr yn derbyn y cais ailddosbarthu ac yn anfon y pecynnau coll. Mae'n werth nodi bod yr anfonwr eisoes wedi dechrau'r amserydd cau cysylltiad ar hyn o bryd a, pan dderbynnir cais, caiff ei ailosod.
Ail-anfon pecynnau coll (cyflwr 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));
}

Mae'r pecyn digio (pecyn #3 yn y diagram) yn cael ei dderbyn gan y cysylltiad sy'n dod i mewn. Gwneir gwiriad i weld a yw'r ffenestr dderbyn yn llawn a bod trosglwyddiad data arferol yn cael ei adfer.
Gwirio am drawiadau yn y ffenestr dderbyn (cyflwr cydosod):

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 CDU dibynadwy

Er mwyn rhyngweithio â'r protocol trosglwyddo data, mae dosbarth Udp Dibynadwy agored, sy'n lapio dros y bloc rheoli trosglwyddo. Dyma aelodau pwysicaf y dosbarth:

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

Derbynnir negeseuon trwy danysgrifiad. Llofnod cynrychiolydd ar gyfer dull galw'n ôl:

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

Neges:

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

I danysgrifio i fath penodol o neges a/neu anfonwr penodol, defnyddir dau baramedr dewisol: ReliableUdpMessageTypes messageType ac IPEndPoint ipEndPoint.

Mathau o negeseuon:

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

Anfonir y neges yn asyncronig; ar gyfer hyn, mae'r protocol yn gweithredu model rhaglennu asyncronaidd:

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

Bydd canlyniad anfon neges yn wir - os yw'r neges wedi cyrraedd y derbynnydd yn llwyddiannus ac yn ffug - os cafodd y cysylltiad ei gau erbyn terfyn amser:

public bool EndSendMessage(IAsyncResult asyncResult)

Casgliad

Nid yw llawer wedi'i ddisgrifio yn yr erthygl hon. Mecanweithiau paru edafedd, ymdrin ag eithriadau a gwallau, gweithredu dulliau anfon negeseuon anghydamserol. Ond dylai craidd y protocol, y disgrifiad o'r rhesymeg ar gyfer prosesu pecynnau, sefydlu cysylltiad, a thrin goramseroedd, fod yn glir i chi.

Mae'r fersiwn a ddangosir o'r protocol cyflenwi dibynadwy yn ddigon cadarn a hyblyg i fodloni'r gofynion a ddiffiniwyd yn flaenorol. Ond rwyf am ychwanegu y gellir gwella'r gweithredu a ddisgrifir. Er enghraifft, er mwyn cynyddu trwygyrch a newid cyfnodau amserydd yn ddeinamig, gellir ychwanegu mecanweithiau fel ffenestr llithro a RTT at y protocol, bydd hefyd yn ddefnyddiol gweithredu mecanwaith ar gyfer pennu MTU rhwng nodau cysylltiad (ond dim ond os anfonir negeseuon mawr) .

Diolch am eich sylw, edrychaf ymlaen at eich sylwadau a'ch sylwadau.

PS I'r rhai sydd â diddordeb yn y manylion neu ddim ond eisiau profi'r protocol, y ddolen i'r prosiect ar GitHube:
Prosiect CDU dibynadwy

Dolenni ac erthyglau defnyddiol

  1. Manyleb protocol TCP: yn Saesneg и yn Rwseg
  2. Manyleb protocol CDU: yn Saesneg и yn Rwseg
  3. Trafod protocol y RUDP: drafft-ietf-sigtran-dibynadwy-udp-00
  4. Protocol Data Dibynadwy: rfc908 и rfc1151
  5. Gweithrediad syml o gadarnhad cyflwyno dros y CDU: Cymerwch Reolaeth lwyr O'ch Rhwydweithio Gyda .NET A CDU
  6. Erthygl yn disgrifio mecanweithiau croesi NAT: Cyfathrebu Cymheiriaid ar draws Cyfieithwyr Cyfeiriadau Rhwydwaith
  7. Gweithredu'r model rhaglennu asyncronaidd: Gweithredu Model Rhaglennu Asynchronous CLR и Sut i weithredu patrwm dylunio IAsyncResult
  8. Cludo'r model rhaglennu asyncronig i'r patrwm asyncronaidd sy'n seiliedig ar dasgau (APM yn TAP):
    TPL a Rhaglennu Asynchronous .NET Traddodiadol
    Rhyngopio â Phatrymau a Mathau Asynchronous Eraill

Diweddariad: Diolch maerofp и sidristij am y syniad o ychwanegu tasg at y rhyngwyneb. Nid yw cydnawsedd y llyfrgell â hen systemau gweithredu yn cael ei dorri, oherwydd Mae'r 4ydd fframwaith yn cefnogi gweinydd XP a 2003.

Ffynhonnell: hab.com

Ychwanegu sylw