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