Блокировка записи в сокет TCP и немедленное закрытие - это проблема? - PullRequest
4 голосов
/ 27 августа 2009

У меня есть отправитель, который TCP-соединяется, отправляет блок данных и закрывает сокет. Я хотел бы написать простейшую, но надежную программу, которая делает все вышеперечисленное. Первое, что приходит на ум (например, в .NET, хотя вопрос относится к сокетам в целом):

// assuming LingerOption(false), NoDelay set to whatever
var client = new TcpClient().Connect(server, port);
var stream = client.GetStream();
stream.Write(data, 0, data.Length);
stream.Close();
client.Close();

Теперь к некоторым вопросам, основанным на прочтении различных MSDN и других материалов:

  1. stream.Close () вызывает Socket.Close (). Говорят, что Socket.Close () закрывается немедленно, отбрасывая сеть данные буфера, которые не были отправлены. это плохо. Но, Socket.Write Документация говорит, что если сокет блокирует по умолчанию), Socket.Write заблокирует пока все данные не отправлены. Так нет проблем, верно?
  2. В общем, может быть ситуация, при которой код выше приведет к тому, что получатель не получать все, что было отправлено в записи? (при условии 100% надежности сеть)

Ответы [ 5 ]

4 голосов
/ 30 августа 2009

Абсолютно нормально вызывать Close сразу после Socket.Write. Вот что говорит TCP RFC 793 :

"Закрытие соединений должно быть изящной операцией в том смысле, что ожидающие отправки SEND будут передаваться (и повторно передаваться), как позволяет управление потоком, до тех пор, пока все они не будут обслужены. Таким образом, должно быть приемлемо сделать несколько ОТПРАВИТЬ вызовы, затем ЗАКРЫТЬ и ожидать, что все данные будут отправлены адресату."

Частичная путаница возникает из документации MSDN :

"Если вы используете протокол с установлением соединения, Send будет блокироваться, пока все байты в буфере не будут отправлены "

На самом деле блокировка Write только копирует данные в исходящий буфер и возвращает. В соответствии с RFC 793 поставщик сети несет ответственность за завершение доставки данных после вызова Socket.Close (конечно, если соединение не разорвано).

1 голос
/ 27 августа 2009

Получатель может отправлять сообщения ожидания или повторной передачи отправителю. Это может произойти, если буферы recv получателя заполнены до того, как будет получен весь блок данных, или если в сети будут потеряны пакеты. Поэтому, если вы выйдете из клиента до того, как будут обменены все данные, вы потеряете данные.

Взгляните на эту книгу, чтобы найти общее решение: сетевое программирование в UNIX, том первый, У. Ричард Стивенс.

Да, это UNIX, но в конечном счете он имеет дело с TCP / IP-связью, и это является корнем вышеупомянутого. И извините, это не совсем просто, но и не очень сложно. Это конечный автомат с большим количеством состояний, чем содержится в вашем предложении.

1 голос
/ 27 августа 2009

TcpClient.Close говорит: «Метод Close помечает экземпляр как удаленный и запрашивает, чтобы связанный сокет закрыл TCP-соединение. На основании свойства LingerState TCP-соединение может оставаться открытым некоторое время после вызова метода Close когда данные еще не отправлены. Уведомление не предоставляется, когда базовое соединение завершило закрытие. "

Короче говоря, если вы не играете с LingerState, вызов Close не приведет к сбросу буфера данных ОС - стек TCP будет стараться доставить записанные вами данные.

Обязательно перехватывайте исключения, однако, любой из ваших вызовов Write или 2 Close может вызвать исключение, если дела пойдут плохо - и убедитесь, что в этих случаях вы используете сокет и поток.

Обратите внимание, что вызов Write() может не блокироваться до тех пор, пока данные не будут отправлены, он будет блокироваться только до тех пор, пока данные не будут скопированы в стек TCP (и при слишком большом количестве вещей TCP может частично отправить некоторые из этих данных в сети до того, как Write вернется)

1 голос
/ 27 августа 2009

если получатель не доступен для чтения, вы можете потерять некоторую часть данных, и это зависит от размера буферов, используемых на нижних уровнях.

Но если при чтении получатель заблокирован, проблем не возникает, пока вы предоставляете достаточно буфера при чтении.

Предполагая надежную сеть и выполняя «Чтение» перед «Запись» и достаточное количество буфера на стороне получателя, этот код абсолютно надежен!

Но решение!:

Как я уже сказал, размер буфера на стороне получателя действительно имеет значение. если в буфере недостаточно места, вы потеряете этот код!

Итак, у вас есть три варианта:

  1. Просто выделите буфер с достаточным размером.
  2. Используйте библиотеку или протоколы более высокого уровня для передачи данных (например, у нас есть функция TransmitFile в Win32 API для передачи файлов)
  3. Реализуйте свой протокол, который перед отправкой данных, отправляет размер требуемого буфера, а затем отправляет фактические данные.

Если вы знаете максимальный размер данных, то я предлагаю вам 1 Иначе, если вам не хватает времени и не интересует протоколы, то я предлагаю вам 2 Еще я предлагаю вам 3

Удачи;)

0 голосов
/ 28 августа 2009

Это обходит ваш первоначальный вопрос, но вы не сказали, какие данные вы отправляли. Почему вы решили использовать TCP-соединение в первую очередь? Как часто вы пишете эти блоки данных? Если это очень часто, я бы порекомендовал одну из двух вещей:

1) Держите TCP.Stream открытым. В противном случае вы можете исчерпать стек TCP доступных портов источника (1025-65535), если отправите более ~ 64000 блоков менее чем за 4 минуты. Это ухудшится, если на исходном компьютере будут открыты другие приложения. Как только это произойдет, ваше приложение будет зависать или давать ошибку, пока не станет доступен первый порт старше 4 минут.

2) Использовать UDP вместо TCP. Нет гарантии доставки, но если это данные в реальном времени, то вы не хотите использовать TCP, поскольку это может значительно задержать данные в реальном времени.

Если вы используете TCP, Client.Close () должен отправить FIN по стеку TCP, и, если другой хост не вышел из строя или сеть между ними не работала, должна гарантировать доставку.

Независимо от того, используете ли вы TCP или UDP, если вас беспокоит каждый блок данных, прибывающих в место назначения, вы должны создать некоторый уровень приложения, проверяющий, что все блоки прибыли в порядке и не повреждены ...

...