Загубени данни при прекъсната TCP връзка

Сценарият е, че предавам данни от една машина на друга чрез TCP в моята домашна мрежа. Подателят създава TCPClient и записва данни в NetworkStream, върнати от GetStream(). Разбирам, че данните в NetworkStream в крайна сметка се изпращат до буфера на NIC и се предават по физическия носител.

Въпреки това, ако връзката бъде прекъсната, данните в MemoryStream и данните в буфера на NIC ще бъдат загубени, но в моето приложение данните бяха записани в потока и можех наивно да предположа, че данните са изпратени на слушане гнездо, но явно не е така. След като връзката бъде възстановена, приложението ще възобнови изпращането на данни там, където, доколкото му е известно, предаването е било прекъснато, но това не взема предвид данните, загубени в буфера на NIC и MemoryStream обекта.

Има ли някакъв начин за разрешаване на този проблем, освен да напиша собствен протокол на приложния слой?


person Quanta    schedule 18.03.2013    source източник


Отговори (2)


Да предположим, че данните, записани в сокета, ще бъдат гарантирани. Това би означавало, че TCP стекът ще трябва да чака след всеки пакет за потвърждение.

Ясно е, че не става така.

TCP не гарантира пристигане, освен когато успешно сте затворили връзката. Само тогава знаеш, че всичко е получено.

Вероятно трябва да установите протокол за възобновяване в приложния слой. Попитайте другата страна докъде сте стигнали при първата връзка.

Много дискусии в коментарите ме водят до следното уточнение: Ако връзката бъде прекъсната, подателят не може надеждно да знае колко байта са получени от другата страна. Той ще знае надеждно, че не всичко е получено, но не и какво е получено.

person usr    schedule 18.03.2013
comment
Хммм, може и просто да използвам UDP, ако пиша протокол на ниво приложение. Много добър отговор и добри прозрения - благодаря. - person Quanta; 18.03.2013
comment
Този отговор е грешен - TCP не гарантира пристигане, освен когато успешно сте затворили връзката. Едва тогава знаете, че всичко е получено, това няма никакъв смисъл. Затварянето на връзка не променя нищо, когато става въпрос за TCP. Ако данните бъдат изчистени, ще се опита повторно, докато не бъде успешно потвърдено от другата страна. TCP със сигурност гарантира пристигането на пакети, но обработката/действието по тези пакети зависи от приложението. - person Rudi Visser; 18.03.2013
comment
Ако данните бъдат изчистени, ще се опита повторно, докато не бъде успешно потвърдено от другата страна. Това не е вярно. Ами ако връзката не работи за постоянно? Тогава приложението смята, че данните са пристигнали, докато всъщност не са. Кажете ми как бихте гарантирали, че данните наистина пристигат или че надеждна грешка се появява само на позицията в потока, където данните не са получени. Няма да намерите отговор.; Дори Flush да гарантира доставка, доставката на всички други места, където не се извиква flush, няма да бъде гарантирана. Ако напишете 1000 байта, 900 може да пристигнат, 100 може да не. Как ще кажеш? - person usr; 18.03.2013
comment
Е вярно. Приложението никога няма да помисли, че е получен пакет, който не е, потокът ще се провали и ще бъде хвърлено изключение. Това, което казва първото ви изречение, е грешно, то наистина чака след всеки пакет за потвърждение и ако не го получи, ще опита отново. Това не е синхронен процес, други пакети могат да бъдат изпратени неправилно, те просто няма да бъдат получени неправилно (обработени отново от слоя за връзка, той ще преподреди данните преди предаване стека). - person Rudi Visser; 19.03.2013
comment
Ако напишете 1000 байта, ще получите 1000 байта или ще бъде хвърлено изключение. - person Rudi Visser; 19.03.2013
comment
Не, ако запишете 1000 байта, другата страна ще получи първите [1..1000] байта. След това ще бъде хвърлено изключение. Но не знаете колко са получени. Не забравяйте, че TCP не е ориентиран към съобщения. Записването на 1000 байта не ги преобразува магически в неделим пакет. Вие спорите с пакети, но няма пакети в TCP. - person usr; 19.03.2013
comment
Мисля, че тук обсъждаме различни точки. Говоря за гарантирана доставка на ниво протокол, вие говорите на ниво приложение. Ако използвате TCP за отделни съобщения, тогава ще рамкирате съобщенията си и ако цялата рамка на съобщението не бъде получена в своята цялост, връзката вероятно ще бъде прекъсната, което ще доведе до известие за грешка от страна на клиента. Както е посочено в моя отговор, внедряването на ваш собствен ACK вероятно би било най-доброто решение за осигуряване на доставка на ниво приложение. (и има пакети в TCP, само не на ниво приложение, тъй като е представено като поток) - person Rudi Visser; 19.03.2013
comment
Моята основна гледна точка е, че изпращачът никога не може да знае колко байта са получени, в случай че открие грешка във връзката. Просто няма начин да разберете къде да възобновите според гаранциите на TCP. - person usr; 19.03.2013
comment
Може да пропускам нещо тук, но flush всъщност не прави нищо на MemoryStream според MSDN документите.. msdn.microsoft.com/en-us/library/ Също така, ако flush действително е записал данни на основното устройство, това все още не е гаранция, че данните бяха изпратени :/ Това е смисълът, който се опитвах да направя в първоначалния въпрос. - person Quanta; 19.03.2013

Има няколко варианта. Един от начините вероятно би бил ръчно да изчистите данните по тръбата на вашия NetworkStream след всяка партида от записани данни.

Въпреки че това може да се разглежда като пречка за производителността, в зависимост от вашия тип приложение, това ще бъде най-устойчивият на грешки начин да го направите. Като извикате ръчно .Flush(), ще разберете дали данните са изпратени успешно по тръбата или не.

Ако правите това на групи от вашия MemoryStream в NetworkStream, можете да съхраните „последните записани байтове“ и да можете да ги поставите обратно във вашия MemoryStream, готови за повторен опит, когато връзката бъде възстановена.

Друг вариант би бил да приложите свой собствен протокол, при свързване незабавно да изпратите на клиента колко байта сървърът е получил до момента (т.е. от последната връзка). Когато имате тази информация, можете да изчистите първите X байта от вашите MemoryStream (тези, които вече са изпратени) и да продължите да натискате останалите данни.

Едно последно нещо, което можете да направите, което може да е най-добрият начин, е комбинация от двете. Основно прилагане на протокол за изпращане/потвърждение. Това означава, че ще изпратите част от данните и ще изчакате сървърът да изпрати обратно ACK/OK. Когато това стане, можете да сте сигурни, че сървърът е получил тези данни, но ако не е, можете да продължите да опитвате отново, докато не получите това ACK.

person Rudi Visser    schedule 18.03.2013
comment
Flush не гарантира доставка, защото не чака потвърждение. - person usr; 18.03.2013
comment
Самото естество на TCP протокола гарантира доставка, тъй като той ще продължи да се опитва да изпрати пакета. Ако не бъде изпратено след това извикване на Flush на слоя за връзка, тази грешка ще се появи в приложението. - person Rudi Visser; 18.03.2013
comment
Не гарантира доставка (как би могло това да е възможно в лицето на неизправни мрежи?!). Приложението ще бъде уведомено, но в произволна точка, а не в точката, където данните все още са били получени от другата страна. Ако не сте съгласни, моля, кажете как би било възможно да се приложи това. - person usr; 19.03.2013