Настроить тайм-аут ACK сокета? - PullRequest
15 голосов
/ 06 октября 2011

Есть ли способ настроить время ожидания, в течение которого сокет ожидает получения ACK для отправленных данных, прежде чем он решит, что соединение не удалось?

Я знаю, что это может быть сделано и на уровне приложения, но, так как каждый пакет, который я отправляю, в любом случае ACK, и я просто хочу знать, получены ли мои данные, используя дополнительные данные на уровне приложения для выполнить то же самое кажется расточительным. (Не говоря уже о том, что в моем конкретном приложении используются платные сотовые линии связи с байтами).

Примечание: Согласно моему предыдущему вопросу - Какие условия вызывают блокировку NetworkStream.Write? - вы не можете полагаться на .Write, генерирующую исключение для определения того, что данные не отправляются должным образом.

Ответы [ 4 ]

6 голосов
/ 06 января 2014

В некоторых RFC IETF упоминается «время ожидания пользователя» ( 5482 793 ), которое выполняет то, что запрашивается.

Некоторые другие операционные системы поддерживают это как параметр сокета, но, к сожалению, не Windows.

Без этой опции единственным способом сократить время до прерывания в этом сценарии было бы уменьшение количествапопытки повторной передачи или уменьшение начального RTT.

В Windows можно управлять первым (для всей машины ..) через netsh / registry: Tcp Max Data Retransmissions .

Возможно ли просто отказаться от текущего соединения через ваш собственный тайм-аут и создать другое, если это необходимо?

  • Приложения должны будут установить, когда соединение должно быть разорвано - возможно, некоторое «время жизни»устанавливается в начале диалога по протоколу TCP на основе времени бездействия или эффективной скорости передачи данных
  • Из-за повторных передач из старого соединения может возникнуть небольшая перегрузка данных
  • Может потребоваться приложение сервераизменено так, чтобы принимать более одного одновременного соединения
  • Этот процесс не должен повторяться доокончательно клиентом на случай, если сеть никогда не достигнет скорости, достаточной для вашего тайм-аута
3 голосов
/ 10 января 2014

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

Я надеюсь, что мой опыт может быть полезным, так как у меня были те же мысли, что и у вас (и даже боролся с другими разработчиками в моей команде из-за этого, настаивая на том, что TCP должен выполнить свою работу). На самом деле довольно легко испортить TCP с помощью беспроводных соединений, конфликтующих сетевых MTU и иногда плохо реализованных маршрутизаторов / точек доступа, которые получают ACK преждевременно или в условиях сбоя. Но также потому, что TCP предназначен для потоковой передачи от одного источника к одному месту назначения, а не для обеспечения полнодуплексной транзакционной связи.

Я провел несколько лет, работая на производителя встроенного устройства, и написал на складе полную систему клиент-сервер для беспроводных терминалов со штрих-кодом. Не сотовая связь в этом случае, но Wi-Fi может быть таким же плохим (но даже WiFi окажется бесполезным для желаемой задачи). К вашему сведению, моя система по-прежнему надежно работает уже сегодня, спустя почти 7 лет, поэтому я считаю, что моя реализация достаточно надежна (она постоянно испытывает помехи от промышленных машин / сварщиков / воздушных компрессоров / мышей, жующих сетевые провода и т. Д.).

Понимание проблемы

@ rodolk опубликовал хорошую информацию. ACK уровня TCP не обязательно соответствуют 1-1 для каждой передачи в сети вашего приложения (и неизменно НЕ будут 1-1, если вы отправляете больше, чем MTU сети или максимальный размер пакета, даже если Nagle отключен).

В конечном итоге механизмы TCP & IP ( Транспортный и Сетевой уровни ) должны обеспечивать доставку вашего трафика в одном направлении (от источника к месту назначения) с некоторыми ограничениями на максимальное количество повторных попыток и т. Д. Связь между приложениями, в конечном счете, связана с полнодуплексной (двусторонней) связью на прикладном уровне , которая устанавливается поверх TCP / IP. Смешивание этих слоев не является хорошей стратегией. Подумайте о HTTP-запросе-ответе поверх TCP / IP. HTTP не полагается на TCP ACKS для реализации своих собственных тайм-аутов и т. Д. HTTP будет хорошей спецификацией для изучения, если вам интересно.

Но давайте даже притворимся, что он делал то, что вы хотите. Вы всегда отправляете менее 1 MTU (или максимальный размер пакета) за 1 передачу и получаете ровно 1 ACK. Представьте свою беспроводную среду, и все становится более сложным. Между успешной передачей и соответствующим ACK может возникнуть ошибка!

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

Устройства часто получают лучше, чем они могут передавать. Устройство обычно принимает ваши передачи идеально, отвечает с каким-то «ACK», который передается, но этот беспроводной ACK никогда не достигает своего пункта назначения из-за качества сигнала, расстояния передачи, радиочастотных помех, затухания сигнала, отражения сигнала и т. Д. В промышленном применении это может быть включение тяжелой техники, сварочных аппаратов, холодильников / морозильников, флуоресцентного освещения и т. Д. В городской среде это может быть мобильность внутри сооружений, парковочных гаражей, стальных строительных конструкций и т. Д.

В какой момент в этом сценарии клиент выполняет действие (сохранение / принятие данных или изменение состояния) и в какой момент сервер считает действие успешным (сохранение / принятие данных или изменение состояния)?Это очень трудно надежно решить без дополнительных проверок связи на уровне приложения (иногда включая двусторонний ACK для транзакций, т. Е .: клиент передает, ACKS сервера, клиент ACK ACK :-) Здесь не следует полагаться на ACK уровня TCP, так как онине будет надежно приравниваться к успешной полнодуплексной связи и не обеспечит надежный механизм повторных попыток для вашего приложения.

Метод прикладного уровня для ненадежной беспроводной связи на встроенных устройствах

Нашитехника заключалась в том, что каждое сообщение прикладного уровня было отправлено с двухбайтовым заголовком прикладного уровня, который включал в себя идентификатор пакета # (просто увеличивающееся целое число), длину всего сообщения в байтах и ​​контрольную сумму CRC32 для всего сообщения.Не могу точно вспомнить, но я верю, что мы сделали это за 8 байтов, 2 |2 |4. (В зависимости от максимальной длины сообщения, которую вы хотите поддерживать).

Итак, предположим, что вы подсчитываете запас на складе, подсчитываете товар и насчитываете 5 единиц, терминал штрих-кода отправляет сообщение на серверговоря "Бен насчитал 5 единиц товара 1234".Когда сервер получает сообщение, он будет ждать, пока он не получит полное сообщение, сначала проверит длину сообщения, а затем контрольную сумму CRC32 (если длина совпадает).Если все это прошло, мы отправили ответ приложения на это сообщение (что-то вроде ACK для приложения).В течение этого времени терминал штрих-кода ожидает ACK от сервера и выполнит повторную передачу, если не получит ответ от сервера.Если сервер получает несколько копий одного и того же идентификатора пакета, он может дедуплицировать, отказываясь от незафиксированных транзакций.Однако, если сканер штрих-кода получает ACK от сервера, он затем отправляет серверу еще одну последнюю команду «COMMIT».Поскольку первые 2 сообщения только что подтвердили работоспособность полнодуплексного соединения, фиксация невероятно маловероятна в этот период времени.К вашему сведению, это условие сбоя довольно легко воспроизвести на краю зоны покрытия WiFi, поэтому возьмите свой ноутбук / устройство и отправляйтесь на прогулку, пока Wi-Fi не станет просто «1 бар» или самой низкой скоростью соединения, часто 1 Мбит / с.

Таким образом, вы добавляете 8-байтовый заголовок в начало вашего сообщения и дополнительно добавляете одну дополнительную окончательную передачу сообщения COMMIT, если вам требуется транзакционный запрос / ответ, когда может произойти сбой только одной стороны беспроводной связи.

Будет очень трудно оправдать сохранение 8 байтов на сообщение со сложным прикладным уровнем для системы перехвата транспортного уровня (например, перехват winpcap).Также вы можете или не сможете реплицировать этот транспортный уровень, перехватив другие устройства (возможно, ваша система будет работать на других устройствах в будущем? Android, iOS, Windows Phone, Linux, можете ли вы реализовать одно и то же взаимодействие прикладного уровня для всех этихПлатформы? Я бы сказал, что вы должны иметь возможность реализовывать свое приложение на каждом устройстве независимо от того, как реализован стек TCP.)

Я бы порекомендовал вам отделить уровень приложений от транспортного и сетевого уровней.разделение проблем и жесткий контроль над условиями повторных попыток, тайм-аутами и потенциально измененными состояниями приложений.

3 голосов
/ 09 января 2014

Я не эксперт по C #, но думаю, что могу помочь ответить.Вы пытаетесь получить данные управления уровня TCP из приложения.Это нелегко, и, как и в случае любого протокола прикладного уровня, вам понадобится какой-то ответ прикладного уровня, такой как Request-Response в HTTP.

Проблема в том, что ВСЕ ваши записанные данные были фактически получены другим концом,этот TCP ориентирован на поток.Это означает, что вы можете отправить 1 КБ данных через сокет, что КБ хранится в буфере TCP snd, и что КБ может быть отправлено с 3 сегментами TCP, которые могут быть подтверждены (TCP ACK) полностью или по отдельности.Это асинхронно.Таким образом, в какой-то момент TCP мог бы отправить только 300 байт ваших 1000 КБ данных, просто пример.

Теперь другой вопрос заключается в том, открываете ли вы соединение и закрываете соединение каждый раз, когда отправляете кусокданные (A) или у вас всегда открытое соединение (B).

В (A) это проще, потому что, если соединение не открывается, вот и все.Тайм-аут может занять более одной минуты, но вы отправляете не более нескольких 20-байтовых заголовков IP и (20-байтовых) TCP (иногда более 20 байт для параметров IP и TCP).

В (B) вы поймете успех или неудачу, когда вы хотите отправить данные.Я бы рассмотрел 3 случая:

1- Другой конец сокета закрыт или сбрасывается соединение TCP.В этом случае вы должны немедленно получить ответ об ошибке или, в C, сигнал, указывающий на сломанный канал, и я предполагаю, что это станет исключением в C #.

2-Другой конец становится недоступным и не имеетзакрыл / сбросил сокет.Это трудно обнаружить, поскольку TCP будет отправлять сообщения с истечением времени ожидания, и после нескольких попыток / тайм-аутов он решит, что соединение разорвано.Время ожидания и количество повторных попыток могут настраиваться, но на уровне ОС (для всех приложений).Я не думаю, что вы можете настроить это по сокету.В этом случае ваше приложение не осознает, что отправляет данные.

3-Данные были успешно получены другим концом и подтверждены на уровне TCP.

Сложная часть заключается в том, чтобы как можно быстрее различать (2) и (3).Я предполагаю, что вы спрашиваете об этом.Я не думаю, что есть какая-либо возможность сделать это полностью, если вы не взломаете ядро.

В любом случае, получение ACK от сервера на уровне приложения может означать всего 1 или 2 байта, сообщающих количество полученных данных.Это в дополнение к 20 + 20 байтов для базовых заголовков IP и TCP.

Если есть какая-либо возможность сделать то, что вы говорите, я бы попробовал это, но я никогда не проверял:

Вы можетеиграть с размером буфера отправки и выбрать функцию.Вы можете установить размер буфера отправки сокета с помощью параметров setsockopt и OS_SNDBUF socket.http://msdn.microsoft.com/en-us/library/system.net.sockets.socket_methods(v=vs.110).aspx

Если вы знаете, что всегда собираетесь отправлять 2 КБ, установите размер буфера отправки на 2 КБ.Обычно вы можете изменить его только после подключения.http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.sendbuffersize(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

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

Пока одно TCP-сообщение подтверждено, Select или Poll должны указывать, что сокет доступен для записи, поскольку отправленные данные удаляются из буфера отправки.

Обратите внимание, что этот алгоритм имеет ограничения:

  1. Операционная система может определять минимальный размер буфера.
  2. Если алгоритм возможен, Select и Poll сообщат вам, что сокет доступен для записи, когда доступно буферное пространство, но только одна часть ваших данных была фактически получена и подтверждена другим концом.
  3. Если вы отправляете сообщения переменного размера, это невозможно.

Если вы не можете применить упомянутый алгоритм, вам может потребоваться оплатить дополнительную стоимость дополнительного сообщения TCP с ~ 42 байтами с приложением-layer simple ACK.

Извините, что не смог найти окончательного решения.Возможно, ОС должны реализовать возможность сообщать вам доступные байты буфера, и это решит вашу проблему.

РЕДАКТИРОВАТЬ: я добавляю еще одно предложение из моих комментариев.

Если у вас есть возможность использовать другой процесс с использованием Winpcap, вы можете перехватить ответы TCP с другого конца !!!Например, используя локальный IPC, такой как разделяемая память или просто сокеты, одно приложение может сообщить другому о данных socekt (src IP, src port, dst IP, dst port).Другой второй процесс, называемый процессом мониторинга, может обнаружить ACK, полученный от другой конечной точки, путем отслеживания соединения.Также можно использовать winpcap, ссылаясь на собственный код ...

0 голосов
/ 06 октября 2011

Вы можете использовать TcpClient.SendTimeout для этого.Это приводит к тому, что операции записи выдают SocketException, если указанный тайм-аут истекает до успешного завершения операции.

http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.sendtimeout.aspx

Также см. Эту страницу для получения дополнительной информации о том, как настроить сокетыс более настраиваемыми и надежными таймаутами:

http://msdn.microsoft.com/en-us/library/bbx2eya8.aspx

...