Сокеты Java и Dropped Connections

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

У меня есть библиотека для отправки уведомлений Apple Push на iPhone через Apple gatways (

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

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

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

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

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

Любые предложения?

UPDATE:

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

Оба модульных теста имеют два потока, сервер и клиент. Сервер закрывается, пока клиент отправляет данные без исключения 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");
}

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

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

Closed socket
writing new packets
Finished writing
Run without any errors

Ответ 1

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

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

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

1. socket.shutdownOutput();
2. wait for inputStream.read() to return -1, indicating the peer has also shutdown its socket

Ответ 2

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

enter image description here

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

Ответ 3

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

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

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

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

@Уточнение/вопрос о комментировании

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

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

@Examples

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

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

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

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