Ny maritrano tany am-boalohany an'ny Internet dia nihevitra ny habaka adiresy homogene izay misy ny node tsirairay manana adiresy IP manerantany sy tokana ary afaka mifandray mivantana amin'ny nodes hafa. Ankehitriny ny Internet, raha ny marina, dia manana maritrano hafa - faritra iray misy adiresy IP manerantany ary faritra maro misy adiresy manokana miafina ao ambadiky ny fitaovana NAT.Amin'ity maritrano ity, ny fitaovana ao amin'ny habaka adiresy manerantany ihany no afaka mifandray mora amin'ny olona rehetra ao amin'ny tambajotra satria manana adiresy IP tokana azo alefa maneran-tany izy ireo. Ny node iray amin'ny tambajotra tsy miankina dia afaka mifandray amin'ny node hafa amin'ny tambajotra iray ihany, ary afaka mifandray amin'ny node hafa malaza ao amin'ny habaka adiresy manerantany. Ity fifaneraserana ity dia tena azo atao noho ny fomba fandikana ny adiresin'ny tambajotra. Ny fitaovana NAT, toy ny router Wi-Fi, dia mamorona fidirana an-databatra fandikan-teny manokana ho an'ny fifandraisana mivoaka ary manova ny adiresy IP sy ny laharan'ny seranana ao anaty fonosana. Izany dia ahafahan'ny fifandraisana mivoaka avy amin'ny tambajotra manokana mankany amin'ny mpampiantrano ao amin'ny habaka adiresy manerantany. Saingy miaraka amin'izay koa, matetika ny fitaovana NAT dia manakana ny fifamoivoizana miditra rehetra raha tsy misy fitsipika manokana momba ny fifandraisana miditra.
Ity maritrano amin'ny Internet ity dia mety tsara ho an'ny fifandraisana amin'ny mpanjifa-server, izay ahafahan'ny mpanjifa miditra amin'ny tambajotra tsy miankina, ary manana adiresy manerantany ny mpizara. Saingy miteraka fahasarotana amin'ny fifandraisana mivantana amin'ny nodes roa eo anelanelany isan-karazany tambajotra tsy miankina. Ny fifandraisana mivantana eo amin'ny node roa dia zava-dehibe ho an'ny fampiharana peer-to-peer toy ny fampitana feo (Skype), fahazoana fidirana lavitra amin'ny solosaina (TeamViewer), na lalao an-tserasera.
Ny iray amin'ireo fomba mahomby indrindra amin'ny fametrahana fifandraisana amin'ny mpiara-miasa eo amin'ny fitaovana amin'ny tambajotra tsy miankina samihafa dia antsoina hoe hole punching. Ity teknika ity dia matetika ampiasaina amin'ny fampiharana mifototra amin'ny protocol UDP.
Fa raha mitaky fanaterana angon-drakitra azo antoka ny fampiharanao, ohatra, mamindra rakitra eo amin'ny solosaina ianao, dia hanana fahasahiranana maro ny fampiasana UDP noho ny zava-misy fa ny UDP dia tsy protocole fanaterana azo antoka ary tsy manome ny fandefasana fonosana araka ny filaharany, fa tsy toy ny TCP protocol.
Amin'ity tranga ity, mba hiantohana ny fandefasana fonosana azo antoka, ilaina ny mampihatra protocol layer fampiharana izay manome ny fiasa ilaina ary miasa amin'ny UDP.
Te-hanamarika avy hatrany aho fa misy teknika fanokanana lavaka TCP amin'ny fametrahana fifandraisana TCP eo amin'ny nodes amin'ny tambajotra tsy miankina samihafa, saingy noho ny tsy fahampian'ny fanohanan'ny fitaovana NAT maro, dia matetika no tsy heverina ho fomba lehibe hampifandraisana azy io. nodes toy izany.
Ho an'ny ambiny amin'ity lahatsoratra ity dia hifantoka amin'ny fampiharana ny protocole fanaterana azo antoka aho. Holazaina ao amin'ny lahatsoratra manaraka ny fampiharana ny teknikan'ny fanosehana lavaka UDP.
Fepetra takian'ny protocole
Ny fanaterana fonosana azo itokisana dia nampiharina tamin'ny alàlan'ny mekanika fanehoan-kevitra tsara (ilay antsoina hoe fanekena tsara)
Ilaina ny famindrana angon-drakitra lehibe, i.e. ny protocol dia tsy maintsy misoroka ny fampitana fonosana tsy ilaina
Tokony ho azo atao ny manafoana ny rafitra fanamafisana ny fanaterana (ny fahafahana miasa toy ny protocol UDP "madio")
Fahaizana mampihatra ny fomba baiko, miaraka amin'ny fanamafisana ny hafatra tsirairay
Ny singa fototra amin'ny famindrana angon-drakitra amin'ny protocol dia tsy maintsy hafatra
Ireo fepetra ireo dia mifanandrify indrindra amin'ny fepetra takian'ny Protocol Data Reliable voalaza ao rfc 908 и rfc 1151, ary niantehitra tamin'ireo fenitra ireo aho rehefa namolavola ity protocol ity.
Mba hahatakarana ireo fepetra ireo, andeha hojerentsika ny fotoana famindrana angon-drakitra eo anelanelan'ny node tambajotra roa mampiasa ny protocols TCP sy UDP. Avelao amin'ireo tranga roa ireo dia ho very ny fonosana iray. Famindrana angon-drakitra tsy misy fifandraisana amin'ny TCP:
Araka ny hitanao amin'ny kisary, raha sendra very ny fonosana, ny TCP dia hamantatra ny fonosana very ary hitatitra izany amin'ny mpandefa amin'ny fangatahana ny isan'ny ampahany very. Famindrana angona amin'ny alàlan'ny protocol UDP:
Ny UDP dia tsy manao dingana fitadiavana fatiantoka. Ny fanaraha-maso ny fahadisoana fampitana ao amin'ny protocol UDP dia andraikitry ny fampiharana.
Ny fisavana ny hadisoana ao amin'ny protocol TCP dia vita amin'ny alàlan'ny fametrahana fifandraisana amin'ny node farany, fitehirizana ny toetry ny fifandraisana, manondro ny isan'ny bytes alefa amin'ny lohatenin'ny fonosana tsirairay, ary ny fampahafantarana ny tapakila amin'ny alàlan'ny laharan'ny fanekena.
Fanampin'izany, mba hanatsarana ny fampandehanana (izany hoe fandefasana fizarana mihoatra ny iray nefa tsy nahazo fankasitrahana), ny protocol TCP dia mampiasa ilay antsoina hoe varavarankely fampitana - ny isan'ny bytes ny angona antenain'ny mpandefa ny fizarana.
Raha mila fanazavana fanampiny momba ny protocol TCP dia jereo rfc 793, manomboka amin'ny UDP mankany rfc 768aiza, raha ny marina, no voafaritra.
Avy amin'ireo voalaza etsy ambony ireo dia mazava fa mba hamoronana protocole fandefasana hafatra azo itokisana amin'ny UDP (antsoina hoe UDP azo itokisana), ilaina ny fampiharana ny fomba famindrana angona mitovy amin'ny TCP. izany hoe:
tehirizo ny fifandraisana
mampiasa fanisana fizarana
mampiasa fonosana fanamafisana manokana
mampiasa mekanika fikandrana tsotsotra mba hampitomboana ny fidirana protocol
Ankoatra izany, mila:
famantarana ny fanombohan'ny hafatra, mba hizara loharanon-karena ho an'ny fifandraisana
famantarana ny fiafaran'ny hafatra, hampita ny hafatra voaray amin'ny fampiharana ambony ary hamoaka loharanon'ny protocol
avelao ny protocole manokana amin'ny fifandraisana hanaisotra ny rafitra fanamafisana fanaterana mba hiasa ho UDP "madio".
Lohateny UDP azo itokisana
Tsarovy fa ny datagram UDP dia voarakitra ao anaty datagrama IP. Ny fonosana UDP azo itokisana dia "voafono" amin'ny datagrama UDP. Encapsulation lohatenin'ny UDP azo itokisana:
Tsotra ny firafitry ny lohatenin'ny UDP azo itokisana:
TransmissionId - ny laharan'ny fampitana, miaraka amin'ny adiresy sy seranan-tsambon'ny mpandray, dia manondro manokana ny fifandraisana
PacketNumber - packet number
Safidy - safidy protocol fanampiny. Raha ny fonosana voalohany dia ampiasaina hanondroana ny haben'ny hafatra
Ny saina dia toy izao manaraka izao:
FirstPacket - ny fonosana voalohany amin'ny hafatra
NoAsk - ny hafatra dia tsy mitaky mekanika fanekena ho alefa
LastPacket - ny fonosana farany amin'ny hafatra
RequestForPacket - fonosana fanamafisana na fangatahana fonosana very
Fitsipika ankapoben'ny protocole
Satria ny UDP azo itokisana dia mifantoka amin'ny fampitana hafatra azo antoka eo anelanelan'ny node roa, dia tsy maintsy afaka mametraka fifandraisana amin'ny ilany iray izy. Mba hametrahana fifandraisana dia mandefa fonosana miaraka amin'ny saina FirstPacket ny mpandefa, ny valiny dia midika fa ny fifandraisana dia napetraka. Ny fonosana valim-panontaniana rehetra, na, amin'ny teny hafa, ny fonosana fankasitrahana, dia mametraka foana ny sandan'ny saha PacketNumber ho iray mihoatra ny sandan'ny PacketNumber lehibe indrindra amin'ny fonosana voaray. Ny saha Options ho an'ny fonosana voalohany nalefa dia ny haben'ny hafatra.
Mekanisma mitovy amin'izany no ampiasaina hamarana ny fifandraisana. Ny saina LastPacket dia napetraka amin'ny fonosana farany amin'ny hafatra. Ao amin'ny fonosan'ny valinteny dia aseho ny laharan'ny fonosana farany + 1, izay midika ho fampitaovana mahomby ny hafatra ho an'ny lafiny fandraisana. Fametrahana fifandraisana sy kisary famaranana:
Rehefa tafapetraka ny fifandraisana dia manomboka ny famindrana angon-drakitra. Ny angona dia alefa amin'ny blocs of packets. Ny sakana tsirairay, afa-tsy ny farany, dia misy fonosana maromaro. Mitovy amin'ny haben'ny fikandrana mandray/mandefa izany. Ny sakana farany amin'ny angona dia mety ho vitsy kokoa ny fonosana. Aorian'ny fandefasana ny sakana tsirairay dia miandry ny fanamafisana ny fandefasana na fangatahana hamerenana indray ny fonosana very ny lafiny fandefasana, ka mamela ny varavarankely fandraisana/fampitana misokatra mba hahazoana valiny. Taorian'ny nahazoana fanamafisana ny fanaterana sakana, dia miova ny varavarankelin'ny fandraisana/fampitana ary alefa ny sakana manaraka.
Ny lafiny mpandray dia mandray ny fonosana. Ny fonosana tsirairay dia voamarina raha toa ka tafiditra ao anatin'ny varavarankely fampitana. Ny fonosana sy ny dika mitovy izay tsy latsaka ao anaty varavarankely dia voasivana. SATRIA Raha toa ka raikitra ny haben'ny varavarankely ary mitovy amin'ny mpandray sy ny mpandefa, dia raha misy sakana misy fonosana tsy misy fatiantoka, dia afindra ny varavarankely mba handraisana ny fonosana amin'ny sakana manaraka ary ny fanamafisana ny fandefasana dia nalefa. Raha tsy feno ny varavarankely ao anatin'ny fe-potoana voatondron'ny fameram-potoana miasa, dia hatomboka ny fisavana izay mbola tsy naterina ny fonosana ary halefa ny fangatahana famerenana. Diagrama fandefasana indray:
Timeouts sy timers protocole
Misy antony maromaro tsy ahafahana mametraka fifandraisana. Ohatra, raha ivelan'ny aterineto ny mpandray anjara. Amin'ity tranga ity, rehefa miezaka manangana fifandraisana, dia hikatona ny fifandraisana rehefa tapitra ny fotoana. Ny fampiharana UDP azo ianteherana dia mampiasa fameram-potoana roa mba hametrahana fe-potoana. Ny voalohany, ny fameram-potoana miasa, dia ampiasaina hiandry valiny avy amin'ny mpampiantrano lavitra. Raha mirehitra eo amin'ny lafiny mpandefa ilay izy, dia alefa indray ilay fonosana nalefa farany. Raha lany ny fameram-potoana ao amin'ny mpandray, dia atao ny fanamarinana ny fonosana very ary alefa ny fangatahana fanaterana.
Ny fameram-potoana faharoa dia ilaina hanidy ny fifandraisana raha sendra tsy fahampian'ny fifandraisana eo amin'ireo nodes. Ho an'ny lafiny mpandefa dia manomboka avy hatrany rehefa tapitra ny fameram-potoana miasa, ary miandry valiny avy amin'ny node lavitra. Raha tsy misy valiny ao anatin'ny fe-potoana voafaritra dia tapaka ny fifandraisana ary avoaka ny loharano. Ho an'ny lafiny fandraisana, manomboka ny fameram-potoana akaiky ny fifandraisana rehefa tapitra indroa ny fameram-potoana miasa. Ilaina izany mba hiantohana ny fahaverezan'ny fonosana fanamafisana. Rehefa tapitra ny fameram-potoana dia tapaka ihany koa ny fifandraisana ary mivoaka ny loharano.
Sarin'ny fanjakana fifindran'ny UDP azo itokisana
Ny fitsipiky ny protocol dia ampiharina amin'ny milina fanjakana voafetra, ny fanjakana tsirairay dia tompon'andraikitra amin'ny lojika sasany amin'ny fanodinana fonosana.
Diagramm-panjakana UDP azo itokisana:
mihidy - tsy tena fanjakana fa toerana fanombohana sy fiafaran'ny automatique. Ho an’ny fanjakana mihidy voaray ny sakana fanaraha-maso ny fandefasana, izay, amin'ny fampiharana ny mpizara UDP asynchronous, dia mandefa ny fonosana amin'ny fifandraisana mety ary manomboka ny fanodinana fanjakana.
FirstPacketSending – ny fanjakana voalohany misy ny fifandraisana mivoaka rehefa alefa ny hafatra.
Amin'ity fanjakana ity dia alefa ny fonosana voalohany ho an'ny hafatra mahazatra. Ho an'ny hafatra tsy misy fanamafisana ny fandefasana dia io ihany no fanjakana handefasana ny hafatra manontolo.
SendingCycle – toerana ifotony amin'ny fampitana ny fonosana hafatra.
Tetezamita ho azy avy amin'ny fanjakana FirstPacketSending natao taorian'ny nandefasana ny fonosana voalohany amin'ny hafatra. Ao amin'io fanjakana io no tonga ny fanekena sy ny fangatahana fampitana indray. Ny fivoahana amin'izany dia azo atao amin'ny tranga roa - raha toa ka mahomby ny fandefasana ny hafatra na amin'ny fe-potoana.
FirstPacketReceived - ny fanjakana voalohany ho an'ny mpandray ny hafatra.
Izy io dia manamarina ny fahamarinan'ny fiandohan'ny fampitana, mamorona ny rafitra ilaina, ary mandefa fanekena ny fahazoana ny fonosana voalohany.
Ho an'ny hafatra iray misy fonosana tokana ary nalefa tsy nampiasa porofo momba ny fandefasana dia io ihany no fanjakana. Rehefa vita ny fanodinana hafatra toy izany dia mikatona ny fifandraisana.
mivory - fanjakana fototra handraisana ny fonosan'ny hafatra.
Izy io dia manoratra fonosana ho any amin'ny fitahirizana vonjimaika, manamarina ny fahaverezan'ny fonosana, mandefa fankasitrahana amin'ny fandefasana andiana fonosana sy ny hafatra iray manontolo, ary mandefa fangatahana hamerenana ny fonosana very. Raha toa ka mahomby ny fandraisana ny hafatra manontolo, ny fifandraisana dia miditra amin'ny fanjakana vita, raha tsy izany dia hivoaka ny fe-potoana.
vita – fanakatonana ny fifandraisana raha toa ka nahomby ny fandraisana ny hafatra manontolo.
Ity fanjakana ity dia ilaina amin'ny fivorian'ny hafatra sy amin'ny tranga raha very ny fanamafisana ny fandefasana ny hafatra teny an-dalana mankany amin'ny mpandefa. Ity fanjakana ity dia miala amin'ny fe-potoana, fa ny fifandraisana dia heverina ho nikatona soa aman-tsara.
Lalina kokoa amin'ny code. rafitra fanaraha-maso ny fifindran'ny
Ny iray amin'ireo singa manan-danja amin'ny UDP azo itokisana dia ny sakana fanaraha-maso ny fifindrana. Ny asan'ity sakana ity dia ny mitahiry ny fifandraisana misy ankehitriny sy ny singa fanampiny, mizara ny fonosana miditra amin'ny fifandraisana mifanaraka amin'izany, manome interface tsara handefasana fonosana amin'ny fifandraisana, ary mampihatra ny protocol API. Ny sakana fanaraha-maso ny fifindrana dia mandray fonosana avy amin'ny sosona UDP ary mandefa azy ireo any amin'ny milina fanjakana ho an'ny fanodinana. Mba hahazoana packet dia mampiasa mpizara UDP asynchronous izy. Ny mpikambana sasany ao amin'ny kilasy 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;
//...
}
Fampiharana ny mpizara UDP asynchronous:
private void Receive()
{
EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
// создаем новый буфер, для каждого socket.BeginReceiveFrom
byte[] buffer = new byte[DefaultMaxPacketSize + ReliableUdpHeader.Length];
// передаем буфер в качестве параметра для асинхронного метода
this.m_socketIn.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref connectedClient, EndReceive, buffer);
}
private void EndReceive(IAsyncResult ar)
{
EndPoint connectedClient = new IPEndPoint(IPAddress.Any, 0);
int bytesRead = this.m_socketIn.EndReceiveFrom(ar, ref connectedClient);
//пакет получен, готовы принимать следующий
Receive();
// т.к. простейший способ решить вопрос с буфером - получить ссылку на него
// из IAsyncResult.AsyncState
byte[] bytes = ((byte[]) ar.AsyncState).Slice(0, bytesRead);
// получаем заголовок пакета
ReliableUdpHeader header;
if (!ReliableUdpStateTools.ReadReliableUdpHeader(bytes, out header))
{
// пришел некорректный пакет - отбрасываем его
return;
}
// конструируем ключ для определения connection record’а для пакета
Tuple<EndPoint, Int32> key = new Tuple<EndPoint, Int32>(connectedClient, header.TransmissionId);
// получаем существующую connection record или создаем новую
ReliableUdpConnectionRecord record = m_listOfHandlers.GetOrAdd(key, new ReliableUdpConnectionRecord(key, this, header.ReliableUdpMessageType));
// запускаем пакет в обработку в конечный автомат
record.State.ReceivePacket(record, header, bytes);
}
Ho an'ny famindrana hafatra tsirairay dia misy rafitra iray mirakitra fampahalalana momba ny fifandraisana. Ny rafitra toy izany dia antsoina hoe firaketana fifandraisana. Ny mpikambana sasany ao amin'ny kilasy 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;
//...
}
Lalina kokoa amin'ny code. Hoy ny
Ny fanjakana dia mampihatra ny milinam-panjakana amin'ny protocol UDP Reliable, izay misy ny fanodinana lehibe ny fonosana. Ny kilasy abstract ReliableUdpState dia manome interface tsara ho an'ny fanjakana:
Ny lojika manontolo amin'ny protocol dia ampiharina amin'ny kilasy aseho etsy ambony, miaraka amin'ny kilasy fanampiny izay manome fomba static, toy ny, ohatra, ny fananganana ny lohatenin'ny ReliableUdp avy amin'ny firaketana fifandraisana.
Manaraka, dia handinika amin'ny an-tsipiriany ny fampiharana ny fomba interface tsara izay mamaritra ny fototry ny algorithms ny protocol.
DisposeByTimeout Method
Ny fomba DisposeByTimeout dia tompon'andraikitra amin'ny famoahana ireo loharanom-pifandraisana aorian'ny fe-potoana ary amin'ny famantarana ny fandefasana hafatra mahomby/tsy nahomby. ReliableUdpState.DisposeByTimeout:
Any amin’ny fanjakana ihany no amboarina vita. Vita.DisposeByTimeout:
protected override void DisposeByTimeout(object record)
{
ReliableUdpConnectionRecord connectionRecord = (ReliableUdpConnectionRecord) record;
// сообщаем об успешном получении сообщения
SetAsCompleted(connectionRecord);
}
Fomba ProcessPackets
Ny fomba ProcessPackets dia tompon'andraikitra amin'ny fanodinana fanampiny amin'ny fonosana na fonosana. Antsoina mivantana na amin'ny alàlan'ny fameram-potoana fiandrasana fonosana.
afaka mivory ny fomba dia nosoloina ary tompon'andraikitra amin'ny fanamarinana ny fonosana very sy ny fifindrana mankany amin'ny fanjakana vita, raha toa ka nahazo ny fonosana farany ary nandalo fanamarinana mahomby Assembling.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.IsDone != 0)
return;
if (!ReliableUdpStateTools.CheckForNoPacketLoss(connectionRecord, connectionRecord.IsLastPacketReceived != 0))
{
// есть потерянные пакеты, отсылаем запросы на них
foreach (int seqNum in connectionRecord.LostPackets)
{
if (seqNum != 0)
{
ReliableUdpStateTools.SendAskForLostPacket(connectionRecord, seqNum);
}
}
// устанавливаем таймер во второй раз, для повторной попытки передачи
if (!connectionRecord.TimerSecondTry)
{
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// если после двух попыток срабатываний WaitForPacketTimer
// не удалось получить пакеты - запускаем таймер завершения соединения
StartCloseWaitTimer(connectionRecord);
}
else if (connectionRecord.IsLastPacketReceived != 0)
// успешная проверка
{
// высылаем подтверждение о получении блока данных
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.State = connectionRecord.Tcb.States.Completed;
connectionRecord.State.ProcessPackets(connectionRecord);
// вместо моментальной реализации ресурсов
// запускаем таймер, на случай, если
// если последний ack не дойдет до отправителя и он запросит его снова.
// по срабатыванию таймера - реализуем ресурсы
// в состоянии Completed метод таймера переопределен
StartCloseWaitTimer(connectionRecord);
}
// это случай, когда ack на блок пакетов был потерян
else
{
if (!connectionRecord.TimerSecondTry)
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
connectionRecord.WaitForPacketsTimer.Change(connectionRecord.ShortTimerPeriod, -1);
connectionRecord.TimerSecondTry = true;
return;
}
// запускаем таймер завершения соединения
StartCloseWaitTimer(connectionRecord);
}
}
afaka SendingCycle Ity fomba ity dia antsoina amin'ny fameram-potoana ihany, ary tompon'andraikitra amin'ny fandefasana ny hafatra farany, ary koa ny fampandehanana ny fameram-potoana akaiky ny fifandraisana. 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);
}
afaka vita Ny fomba dia manakana ny fampandehanana fotoana ary mandefa ny hafatra ho an'ny mpanjifa. Vita.ProcessPackets:
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
if (connectionRecord.WaitForPacketsTimer != null)
connectionRecord.WaitForPacketsTimer.Dispose();
// собираем сообщение и передаем его подписчикам
ReliableUdpStateTools.CreateMessageFromMemoryStream(connectionRecord);
}
ReceivePacket fomba
afaka FirstPacketReceived Ny tena asa ny fomba dia ny hamaritana raha tonga teo amin'ny interface tsara ny fonosana hafatra voalohany, ary koa ny fanangonana hafatra misy fonosana tokana. 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);
}
}
afaka SendingCycle io fomba io dia nosoloina mba hanaiky ny fanekena fanaterana sy ny fangatahana fandefasana indray. 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));
}
afaka mivory amin'ny fomba ReceivePacket, ny asa lehibe amin'ny fanangonana hafatra avy amin'ny fonosana miditra dia mitranga. 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);
}
}
afaka vita Ny hany andraikitry ny fomba dia ny mandefa indray ny fanekena ny fahombiazan'ny fandefasana ny hafatra. Vita. ReceivePacket:
public override void ReceivePacket(ReliableUdpConnectionRecord connectionRecord, ReliableUdpHeader header, byte[] payload)
{
// повторная отправка последнего пакета в связи с тем,
// что последний ack не дошел до отправителя
if (header.Flags.HasFlag(ReliableUdpHeaderFlags.LastPacket))
{
ReliableUdpStateTools.SendAcknowledgePacket(connectionRecord);
}
}
Fomba fandefasana fonosana
afaka FirstPacketSending Ity fomba ity dia mandefa ny fonosana voalohany misy angon-drakitra, na raha tsy mila fanamafisana ny fandefasana ny hafatra, dia ny hafatra manontolo. 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);
}
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 );
}
}
Lalina kokoa amin'ny code. Famoronana sy fametrahana fifandraisana
Ankehitriny rehefa nahita ny fanjakana fototra sy ny fomba ampiasaina amin'ny fitantanana fanjakana isika, andao hanazava ohatra vitsivitsy momba ny fomba fiasan'ny protocol amin'ny antsipiriany bebe kokoa. Diagram fampitana angon-drakitra ao anatin'ny fepetra mahazatra:
Diniho amin'ny an-tsipiriany ny famoronana firaketana fifandraisana mampifandray sy mandefa ny fonosana voalohany. Ny famindrana dia atomboka hatrany amin'ny fampiharana izay miantso ny fandefasana hafatra API. Avy eo, ny fomba StartTransmission amin'ny sakana fanaraha-maso ny fifindrana dia antsoina, izay manomboka ny fandefasana angon-drakitra ho an'ny hafatra vaovao. Mamorona fifandraisana mivoaka:
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]);
}
Mandefa ny fonosana voalohany (FirstPacketSending state):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
connectionRecord.PacketCounter = 0;
connectionRecord.SndNext = 0;
connectionRecord.WindowLowerBound = 0;
// ...
// создаем заголовок пакета и отправляем его
ReliableUdpHeader header = ReliableUdpStateTools.CreateReliableUdpHeader(connectionRecord);
ReliableUdpStateTools.SendPacket(connectionRecord, ReliableUdpStateTools.CreateUdpPayload(connectionRecord, header));
// увеличиваем счетчик
connectionRecord.SndNext++;
// сдвигаем окно
connectionRecord.WindowLowerBound++;
// переходим в состояние SendingCycle
connectionRecord.State = connectionRecord.Tcb.States.SendingCycle;
// Запускаем таймер
connectionRecord.WaitForPacketsTimer = new Timer(CheckByTimer, connectionRecord, connectionRecord.ShortTimerPeriod, -1);
}
Aorian'ny fandefasana ny fonosana voalohany dia miditra amin'ny fanjakana ny mpandefa SendingCycle - miandry ny fanamafisana ny fandefasana entana.
Ny lafiny fandraisana, mampiasa ny fomba EndReceive, dia mandray ny fonosana nalefa, mamorona vaovao firaketana fifandraisana ary ampita ity fonosana ity, miaraka amin'ny lohapejy efa voaomana mialoha, mankany amin'ny fomba ReceivePacket an'ny fanjakana ho an'ny fanodinana FirstPacketReceived Mamorona fifandraisana amin'ny lafiny fandraisana:
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);
}
Mandray ny fonosana voalohany ary mandefa fanekena (FirstPacketReceived fanjakana):
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);
}
}
Lalina kokoa amin'ny code. Manakatona ny fifandraisana rehefa tapitra ny fotoana
Ampahany manan-danja ao amin'ny UDP azo itokisana ny fitantanana ny fotoana. Diniho ohatra iray izay tsy nahomby ny node mpanelanelana ary nanjary tsy azo atao ny fandefasana angona amin'ny lafiny roa. Diagram amin'ny fanakatonana fifandraisana amin'ny fe-potoana:
Araka ny hita amin'ny kisary dia manomboka avy hatrany ny fameram-potoana fiasan'ny mpandefa rehefa avy nandefa andiana fonosana. Izany dia mitranga amin'ny fomba SendPacket an'ny fanjakana SendingCycle. Fampandehanana ny fameram-potoana (fanjakana SendingCycle):
public override void SendPacket(ReliableUdpConnectionRecord connectionRecord)
{
// отправляем блок пакетов
// ...
// перезапускаем таймер после отправки
connectionRecord.WaitForPacketsTimer.Change( connectionRecord.ShortTimerPeriod, -1 );
if ( connectionRecord.CloseWaitTimer != null )
connectionRecord.CloseWaitTimer.Change( -1, -1 );
}
Ny fe-potoana fameram-potoana dia napetraka rehefa noforonina ny fifandraisana. Ny ShortTimerPeriod default dia 5 segondra. Ao amin'ny ohatra, dia napetraka ho 1,5 segondra.
Ho an'ny fifandraisana miditra, manomboka ny fameram-potoana aorian'ny fandraisana ny packet data miditra farany, izany dia mitranga amin'ny fomba ReceivePacket an'ny fanjakana. mivory Fampandehanana ny fameram-potoana (fanjakana fanangonana):
Tsy nisy fonosana tonga teo amin'ny fifandraisana tonga teo am-piandrasana ny fameram-potoana miasa. Nandeha ny fameram-potoana ary niantso ny fomba ProcessPackets, izay nahitana ireo fonosana very ary nalefa voalohany ny fangatahana famerenana. Fandefasana fangatahana fanaterana indray (fanjakana fanangonana):
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);
}
}
Ny variable TimerSecondTry dia napetraka amin'ny marina. Ity faribolana ity dia tompon'andraikitra amin'ny famerenana ny fameram-potoana miasa.
Eo amin'ny sisin'ny mpandefa, ny fameram-potoana miasa ihany koa dia alefa ary alefa indray ny fonosana nalefa farany. Mandeha ny famerana akaiky ny fifandraisana (fanjakana SendingCycle):
public override void ProcessPackets(ReliableUdpConnectionRecord connectionRecord)
{
// ...
// отправляем повторно последний пакет
// ...
// включаем таймер CloseWait – для ожидания восстановления соединения или его завершения
StartCloseWaitTimer(connectionRecord);
}
Aorian'izay dia manomboka amin'ny fifandraisana mivoaka ny fameram-potoana akaiky ny fifandraisana. 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);
}
Ny fe-potoana fiatoan'ny fameram-potoana fanakatonana dia 30 segondra raha ny mahazatra.
Rehefa afaka fotoana fohy, dia mirehitra indray ny fameram-potoana miasa eo amin'ny lafiny mpandray, alefa indray ny fangatahana, aorian'izay dia manomboka ny famerana akaiky ny fifandraisana ho an'ny fifandraisana miditra.
Rehefa mirehitra ny fameram-potoana akaiky, dia avoaka daholo ny loharanon'ny firaketana an-tsoratra roa. Ny mpandefa dia mitatitra ny tsy fahombiazan'ny fandefasana amin'ny fampiharana ambony (jereo UDP API azo itokisana). Famoahana loharanon-drakitra momba ny fifandraisana:
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);
}
}
Lalina kokoa amin'ny code. Famerenana ny famindrana angona
Diagram fanarenana fampitana angon-drakitra raha sendra very ny fonosana:
Araka ny efa noresahina teo amin'ny fanakatonana ny fifandraisana amin'ny fiafaran'ny fotoana, rehefa tapitra ny fameram-potoana miasa, dia hanamarina ny entana very ny mpandray. Raha sanatria very packet dia hangonina ny lisitry ny isan'ny packet tsy tonga any amin'ny mpandray. Ireo isa ireo dia ampidirina ao amin'ny laharan'ny LostPackets amin'ny fifandraisana manokana, ary alefa ny fangatahana fanaterana. Fandefasana fangatahana hanaterana indray ny fonosana (fanjakana mitambatra):
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);
}
}
// ...
}
}
Hanaiky ny fangatahana fanaterana indray ny mpandefa ary handefa ny fonosana tsy hita. Tsara ny manamarika fa amin'izao fotoana izao ny mpandefa dia efa nanomboka ny fifandraisana akaiky timer ary, rehefa misy fangatahana voaray, dia reset. Mandefa indray ny fonosana very (fanjakana 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));
}
Ny fonosana resent (fonosana #3 amin'ny kisary) dia raisina amin'ny fifandraisana miditra. Ny fanamarinana dia atao mba hahitana raha feno ny varavarankely fandraisana ary haverina amin'ny laoniny ny fandefasana angon-drakitra. Fanamarinana ireo hitsaoka ao amin'ny varavarankely fandraisana (Fanjakana fanangonana):
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 azo itokisana
Mba hifaneraserana amin'ny protocol transfer data dia misy kilasy Udp azo itokisana misokatra, izay fonosina amin'ny sakana fanaraha-maso ny famindrana. Ireto ny mpikambana manan-danja indrindra amin'ny kilasy:
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()
}
Ny hafatra dia raisina amin'ny alàlan'ny famandrihana. Alefaso sonia ho an'ny fomba fiantsoana:
public delegate void ReliableUdpMessageCallback( ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteClient );
Сообщение:
public class ReliableUdpMessage
{
// тип сообщения, простое перечисление
public ReliableUdpMessageTypes Type { get; private set; }
// данные сообщения
public byte[] Body { get; private set; }
// если установлено в true – механизм подтверждения доставки будет отключен
// для передачи конкретного сообщения
public bool NoAsk { get; private set; }
}
Raha te hisoratra anarana amin'ny karazana hafatra manokana sy/na mpandefa manokana, dia misy masontsivana roa azo ampiasaina: ReliableUdpMessageTypes messageType sy IPEndPoint ipEndPoint.
Karazana hafatra:
public enum ReliableUdpMessageTypes : short
{
// Любое
Any = 0,
// Запрос к STUN server
StunRequest = 1,
// Ответ от STUN server
StunResponse = 2,
// Передача файла
FileTransfer =3,
// ...
}
Ny hafatra dia alefa asynchronous; noho izany, ny protocol dia mametraka modely fandaharana asynchronous:
public IAsyncResult BeginSendMessage(ReliableUdpMessage reliableUdpMessage, IPEndPoint remoteEndPoint, AsyncCallback asyncCallback, Object state)
Ny valin'ny fandefasana hafatra dia ho marina - raha toa ka tonga soa aman-tsara any amin'ny mpandray ny hafatra ary diso - raha nikatona ny fifandraisana tamin'ny fe-potoana:
public bool EndSendMessage(IAsyncResult asyncResult)
famaranana
Be dia be no tsy voalaza ato amin'ity lahatsoratra ity. Mekanisma mifanandrify amin'ny kofehy, maningana sy fitantanana ny fahadisoana, fampiharana ny fomba fandefasana hafatra asynchronous. Fa ny fototry ny protocole, ny famaritana ny lojika ho an'ny fanodinana fonosana, ny fametrahana fifandraisana, ary ny fikarakarana ny fe-potoana, dia tokony ho mazava aminao.
Ny dikan-teny nasehon'ny protocole fanaterana azo itokisana dia matanjaka sy azo leferina ampy hahafeno ireo fepetra efa voafaritra teo aloha. Saingy tiako ny manampy fa azo hatsaraina ny fampiharana voalaza. Ohatra, mba hampitomboana ny fampandehanana sy hanovana ny vanim-potoanan'ny fameram-potoana, dia azo ampidirina amin'ny protocol ny mekanika toy ny fikandrana sliding sy ny RTT, ilaina ihany koa ny fampiharana mekanika hamaritana ny MTU eo anelanelan'ny node fifandraisana (fa raha misy hafatra lehibe alefa) .
Misaotra anao amin'ny fiheveranao, manantena ny fanehoan-kevitra sy ny fanehoan-kevitrao aho.
PS Ho an'ireo izay liana amin'ny antsipiriany na te hanandrana ny protocol fotsiny, ny rohy mankany amin'ny tetikasa ao amin'ny GitHube: Tetikasa UDP azo itokisana
Fanavaozana: Misaotra mayorovp и sidristij ho an'ny hevitra hanampiana asa amin'ny interface. Ny mifanaraka amin'ny tranomboky amin'ny rafitra miasa taloha dia tsy voahitsakitsaka, satria Ny rafitra faha-4 dia manohana ny mpizara XP sy 2003.