Intereting Posts
Исключение при попытке вызова (SIP) Когда вызывается onBindViewHolder и как он работает? Ошибка: выполнение выполнено для задачи ': app: mergeDebugResources'. > Не удалось выполнить хэширование файлов, см. Журналы для подробностей Автомасштабирование текста TextView для вставки в пределах границ Экран приложения Android и Pattern Lock Добавление двух панелей инструментов AppCompat с различными темами Android: неправильный onMeasure – много вызовов Ошибка сборки Gradle – исключение NullPointerException, заданное при выполнении задачи градиента app: compileDebugJava Изменение размера зоны отбрасывания для разрушения пузырьков BroadcastReceiver, Service и Wakelock Изменение цвета значка настроек панели действий Совместимость обратного кода Android Ленивая инициализация комплектов Fabric? Как фильтровать конкретные действия, которые нужно протестировать обезьяной с помощью android.intent.category? Алгоритм свай частиц

Более быстрое обнаружение сломанного сокета в Java / Android

Задний план

Мое приложение собирает данные с телефона и отправляет их на удаленный сервер.
Данные сначала сохраняются в памяти (или в файле, когда она достаточно большая), и каждые X секунд или около того приложение очищает эти данные и отправляет их на сервер.

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

проблема

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

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

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

Мне нужен способ убедиться, что данные действительно доходят до сервера.

Возможно, это один из основных вопросов TCP, но не менее, я не нашел для него никакого решения.

Я попробовал

  • Настройка Socket.setTcpNoDelay(true)
  • Удаление всех буферизованных авторов и просто использование OutputStream напрямую
  • Промывка потока после каждой отправки

Дополнительная информация

Я не могу изменить способ ответа сервера. Я не могу сказать серверу о подтверждении данных (больше, чем о механизме TCP), сервер просто молча принимает данные, не отправив ничего обратно.

Фрагмент кода

Инициализация класса:

 socket = new Socket(host, port); socket.setTcpNoDelay(true); 

Когда данные отправляются:

 while(!dataList.isEmpty()) { String data = dataList.removeFirst(); inMemoryCount -= data.length(); try { OutputStream os = socket.getOutputStream(); os.write(data.getBytes()); os.flush(); } catch(IOException e) { inMemoryCount += data.length(); dataList.addFirst(data); socket = null; return false; } } return true; 

Обновление 1

Я скажу это снова, я не могу изменить способ поведения сервера.
Он получает данные через TCP и UPD и не отправляет никаких данных для подтверждения приема. Это факт и уверен, что в идеальном мире сервер будет признавать данные, но этого просто не произойдет.


Обновление 2

Решение, размещенное Fraggle, отлично работает (закрытие сокета и ожидание закрытия входного потока).

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

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

Сама строка не так длинна (около 30 символов), но это добавляется, если я слишком часто закрываю и открываю сокет.

Одним из решений является только «сброс» данных каждые X байтов, проблема в том, что я должен выбирать X с умом; Если слишком большой, будет отправлено слишком много дублированных данных, если сокет опустится, и если он слишком мал, накладные расходы слишком велики.


Окончательное обновление

Моим окончательным решением является «сбросить» сокет, закрыв его каждые X байтов, и если все не получилось, эти X-байты будут отправлены снова.

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

См. Принятый ответ здесь: Java-сокеты и Dropped Connections

  1. socket.shutdownOutput ();
  2. Подождите, пока inputStream.read () вернет -1, указав, что одноранговая сеть также отключила свой сокет

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

Хорошие новости: Как вы говорите, все в порядке, если вы отправляете дубликаты данных. Таким образом, ваше решение не должно беспокоиться о обнаружении сбоя менее чем за 20 секунд, которое требуется сейчас. Вместо этого просто держите круговой буфер, содержащий данные за 30 или 60 секунд. Каждый раз, когда вы обнаруживаете сбой и затем снова соединяетесь, вы можете запустить сеанс, повторно отправив эти сохраненные данные.

(Это может быть проблематично, если сервер несколько раз циклически перемещается вверх и вниз менее чем за минуту, но если это так, у вас есть другие проблемы, с которыми можно справиться.)

Решение Дэна – это тот, который я предлагаю сразу после прочтения вашего вопроса, он получил мой голос.

Теперь я могу предложить решить проблему? Я не знаю, возможно ли это с вашей настройкой, но один из способов иметь дело с плохо спроектированным программным обеспечением (это ваш сервер, извините) – это обернуть его, или в режиме fancy-design-pattern-talk предоставить фасад или в Простой разговор поставил прокси-сервер перед вашим сервером «боль в заднем». Разработайте осмысленный протокол на основе ack, попросите прокси сохранить достаточное количество выборок данных в памяти, чтобы иметь возможность обнаруживать и терпеть неработающие соединения и т. Д. И т. Д. Короче говоря, подключите телефонное приложение к прокси-серверу, находящемуся где-нибудь на «серверном уровне», Используя «хороший» протокол, затем прокси подключиться к серверному процессу с использованием «плохого» протокола. Клиент отвечает за генерирование данных. Прокси отвечает за работу с сервером.

Еще одна идея.

Изменить 0:

Вы можете найти этот интересный: Конечная страница SO_LINGER или: почему мой tcp не является надежным .

Не будет работать: сервер не может быть изменен

Не может ли ваш сервер подтвердить каждое сообщение, которое он получает с помощью другого пакета? Клиент не удалит сообщения, которые сервер еще не подтвердил.

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

Если вы отправляете сообщение каждые 5 секунд, а отключение не будет обнаружено сетевым стеком в течение 30 секунд, вам нужно будет хранить всего 6 сообщений. Если 6 отправленных сообщений не подтверждены, вы можете считать, что соединение отключено. (Я полагаю, что логика повторного подключения и отправки назад уже реализована в вашем приложении.)

Как насчет отправки датаграмм UDP на отдельный сокет UDP, когда удаленный хост отвечает на них, а затем, когда удаленный хост не отвечает, вы убиваете TCP-соединение? Он достаточно быстро обнаруживает разрыв связи 🙂

Используйте http POST вместо соединения сокета, затем вы можете отправить ответ на каждое сообщение. На стороне клиента вы удаляете только данные из памяти, если ответ указывает на успех.

Уверенный его больше накладных расходов, но дает вам то, что вы хотите в 100% случаев.