Потерянные данные при разрыве TCP-соединения

Сценарий заключается в том, что я передаю данные с одной машины на другую через TCP в своей домашней сети. Отправитель создает экземпляр TCPClient и записывает данные в NetworkStream, возвращенный GetStream(). Насколько я понимаю, данные в NetworkStream в конечном итоге отправляются в буфер на сетевой карте и передаются через физический носитель.

Однако, если соединение прервется, данные в MemoryStream и данные в буфере сетевых карт будут потеряны, но в моем приложении данные были записаны в поток, и я мог наивно предположить, что данные были отправлены на прослушивание. гнездо, но это явно не так. Как только соединение будет восстановлено, приложение возобновит отправку данных там, где, насколько ему известно, передача была прервана, но при этом не учитываются данные, потерянные в буфере сетевой карты и объекте 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
Я мог бы что-то упустить, но согласно документам MSDN, flush на самом деле ничего не делает с MemoryStream. 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