Настроить тайм-аут ACK сокета?

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

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

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

Ответы

Ответ 1

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

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

Я потратил несколько лет на создание встроенного устройства и написал полную клиент-серверную систему для беспроводных терминалов штрих-кода на складе. Не сотовый в этом случае, но wifi может быть столь же плохим (но даже Wi-Fi докажет, что желаемая задача бесполезна). FYI, моя система по-прежнему надежно работает в производстве уже почти 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, клиент ACKS ACK:-) Здесь вы не должны полагаться на ACK уровня TCP, поскольку они не будет надежно соответствовать успешной полнодуплексной связи и не будет способствовать надежному механизму повторной попытки для вашего приложения.

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

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

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

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

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

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

Ответ 2

В некоторых IETF RFC упоминается "тайм-аут пользователя" (5482 793), который делает то, что требуется.

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

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

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

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

  • Приложения должны будут установить, когда соединение должно быть прекращено - возможно, некоторое время для жизни, установленное в начале сеанса TCP, в зависимости от времени бездействия или эффективной скорости передачи данных.
  • В связи с повторными передачами из старого соединения было бы немного накладных данных
  • Серверное приложение может потребоваться изменить, чтобы принять более одного одновременного коннекта
  • Этот процесс не должен повторяться неопределенно клиентом, если сеть никогда не достигает достаточно высокой скорости для вашего тайм-аута

Ответ 3

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

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

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

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

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

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

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

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

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

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

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

Вы можете играть с размером буфера отправки и выбирать функцию. Вы можете установить размер буфера отправки сокета с опцией socketockopt и OS_SNDBUF. 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 на Socket, чтобы проверить, доступна ли запись.

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

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

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

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

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

EDIT: Я добавляю еще одно предложение из моих комментариев.

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

Ответ 4

Вы можете использовать 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