Java-сокеты и удаленные соединения - PullRequest
13 голосов
/ 08 января 2010

Какой самый подходящий способ определить, была ли розетка сброшена или нет? Или пакет действительно был отправлен?

У меня есть библиотека для отправки push-уведомлений Apple на iPhone через шлюзы Apple ( доступно на GitHub ). Клиенты должны открыть сокет и отправить двоичное представление каждого сообщения; но, к сожалению, Apple не возвращает никаких подтверждений. Соединение также можно использовать для отправки нескольких сообщений. Я использую простые соединения Java Socket. Соответствующий код:

Socket socket = socket();   // returns an reused open socket, or a new one
socket.getOutputStream().write(m.marshall());
socket.getOutputStream().flush();
logger.debug("Message \"{}\" sent", m);

В некоторых случаях, если соединение сбрасывается во время отправки сообщения или прямо перед ним; Socket.getOutputStream().write() заканчивается успешно, хотя. Я ожидаю, что это из-за того, что окно TCP еще не исчерпано.

Есть ли способ, которым я могу точно сказать, действительно ли пакет попал в сеть или нет? Я экспериментировал со следующими двумя решениями:

  1. Вставить дополнительную операцию socket.getInputStream().read() с тайм-аутом 250 мс. Это вызывает операцию чтения, которая завершается неудачно при разрыве соединения, но в противном случае зависает в течение 250 мс.

  2. установить размер буфера отправки TCP (например, Socket.setSendBufferSize()) равным двоичному размеру сообщения.

Оба метода работают, но они значительно ухудшают качество обслуживания; Пропускная способность увеличивается от 100 сообщений в секунду до максимум 10 сообщений в секунду.

Есть предложения?

UPDATE:

Опровергнуто несколько ответов, ставящих под сомнение возможность описанного. Я построил «модульные» тесты поведения, которое я описываю. Проверьте случаи устройства в Суть 273786 .

Оба модульных теста состоят из двух потоков: сервера и клиента. Сервер закрывается, пока клиент отправляет данные без IOException в любом случае. Вот основной метод:

public static void main(String[] args) throws Throwable {
    final int PORT = 8005;
    final int FIRST_BUF_SIZE = 5;

    final Throwable[] errors = new Throwable[1];
    final Semaphore serverClosing = new Semaphore(0);
    final Semaphore messageFlushed = new Semaphore(0);

    class ServerThread extends Thread {
        public void run() {
            try {
                ServerSocket ssocket = new ServerSocket(PORT);
                Socket socket = ssocket.accept();
                InputStream s = socket.getInputStream();
                s.read(new byte[FIRST_BUF_SIZE]);

                messageFlushed.acquire();

                socket.close();
                ssocket.close();
                System.out.println("Closed socket");

                serverClosing.release();
            } catch (Throwable e) {
                errors[0] = e;
            }
        }
    }

    class ClientThread extends Thread {
        public void run() {
            try {
                Socket socket = new Socket("localhost", PORT);
                OutputStream st = socket.getOutputStream();
                st.write(new byte[FIRST_BUF_SIZE]);
                st.flush();

                messageFlushed.release();
                serverClosing.acquire(1);

                System.out.println("writing new packets");

                // sending more packets while server already
                // closed connection
                st.write(32);
                st.flush();
                st.close();

                System.out.println("Sent");
            } catch (Throwable e) {
                errors[0] = e;
            }
        }
    }

    Thread thread1 = new ServerThread();
    Thread thread2 = new ClientThread();

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();

    if (errors[0] != null)
        throw errors[0];
    System.out.println("Run without any errors");
}

[Кстати, у меня также есть библиотека тестирования параллелизма, которая делает настройку немного лучше и понятнее. Оформить заказ также в Gist].

При запуске я получаю следующий вывод:

Closed socket
writing new packets
Finished writing
Run without any errors

Ответы [ 3 ]

17 голосов
/ 09 января 2010

Это не очень поможет вам, но технически оба предложенных вами решения неверны. OutputStream.flush () и любые другие вызовы API, о которых вы можете подумать, не будут делать то, что вам нужно.

Единственный портативный и надежный способ определить, был ли пакет получен одноранговым узлом, состоит в ожидании подтверждения от однорангового узла. Это подтверждение может быть либо фактическим ответом, либо постепенным отключением сокета. Конец истории - другого пути действительно нет, и это не специфично для Java - это фундаментальное сетевое программирование.

Если это не постоянное соединение, то есть если вы просто отправляете что-то, а затем закрываете соединение, то, как вы это делаете, вы перехватываете все IOException (любое из них указывает на ошибку) и выполняете корректное завершение сокета :

1. socket.shutdownOutput();
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket
3 голосов
/ 20 декабря 2011

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

enter image description here

Таким образом, Apple не прервет соединение, если произойдет ошибка, но запишет код обратной связи в сокет.

2 голосов
/ 10 января 2010

Если вы отправляете информацию с использованием протокола TCP / IP в Apple, вы должны получать подтверждения. Однако вы заявили:

Apple не возвращает подтверждение вообще

Что вы подразумеваете под этим? TCP / IP гарантирует доставку, поэтому получатель ДОЛЖЕН подтвердить получение. Однако, это не гарантирует, когда будет осуществлена ​​доставка.

Если вы отправляете уведомление в Apple и разрываете соединение до получения ACK, вы не можете определить, успешно ли вы достигли или нет, поэтому вы просто должны отправить его снова. Если двойное нажатие одной и той же информации является проблемой или неправильно обрабатывается устройством, значит, есть проблема. Решение состоит в том, чтобы исправить обработку устройства дублирующегося push-уведомления: на стороне нажатия ничего сделать нельзя.

@ Пояснение к комментарию / вопрос

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

Существует ли ситуация, когда данные, по-видимому, "отправляются", но не гарантированно принимаются, когда не возникает исключение? Ответ должен быть нет.

@ Примеры

Хорошие примеры, это немного проясняет ситуацию. Я бы подумал, что будет сгенерирована ошибка. В опубликованном примере выдается ошибка при второй записи, но не при первой. Это интересное поведение ... и я не смог найти много информации, объясняющей, почему он так себя ведет. Это, однако, объясняет, почему мы должны разрабатывать наши собственные протоколы прикладного уровня для проверки доставки.

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

http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...