Можете ли вы объяснить процесс подключения HttpURLConnection?

134

Я использую HTTPURLConnectionдля подключения к веб-сервису. Я знаю, как использовать, HTTPURLConnectionно я хочу понять, как это работает. В основном, я хочу знать следующее:

  • В какой момент HTTPURLConnectionпытается установить соединение с данным URL?
  • В какой момент я могу узнать, что мне удалось успешно установить соединение?
  • Установление соединения и отправка фактического запроса выполняется за один шаг / вызов метода? Какой это метод?
  • Можете ли вы объяснить функцию getOutputStreamи getInputStreamв терминах непрофессионала? Я замечаю, что когда сервер, к которому я пытаюсь подключиться, не работает, я получаю Exceptionat getOutputStream. Означает ли это, что HTTPURLConnectionустановление соединения начнется только тогда, когда я вызову getOutputStream? Как насчет getInputStream? Поскольку я могу получить ответ только по адресу getInputStream, значит ли это, что я еще не отправил запрос, getOutputStreamа просто установил соединение? Есть ли HttpURLConnectionвернуться к серверу для запроса на ответ , когда я призываю getInputStream?
  • Я правильно сказал, что openConnectionпросто создает новый объект подключения, но еще не устанавливает никакого подключения?
  • Как измерить накладные расходы на чтение и накладные расходы на соединение?
Arci
источник

Ответы:

184
String message = URLEncoder.encode("my message", "UTF-8");

try {
    // instantiate the URL object with the target URL of the resource to
    // request
    URL url = new URL("http://www.example.com/comment");

    // instantiate the HttpURLConnection with the URL object - A new
    // connection is opened every time by calling the openConnection
    // method of the protocol handler for this URL.
    // 1. This is the point where the connection is opened.
    HttpURLConnection connection = (HttpURLConnection) url
            .openConnection();
    // set connection output to true
    connection.setDoOutput(true);
    // instead of a GET, we're going to send using method="POST"
    connection.setRequestMethod("POST");

    // instantiate OutputStreamWriter using the output stream, returned
    // from getOutputStream, that writes to this connection.
    // 2. This is the point where you'll know if the connection was
    // successfully established. If an I/O error occurs while creating
    // the output stream, you'll see an IOException.
    OutputStreamWriter writer = new OutputStreamWriter(
            connection.getOutputStream());

    // write data to the connection. This is data that you are sending
    // to the server
    // 3. No. Sending the data is conducted here. We established the
    // connection with getOutputStream
    writer.write("message=" + message);

    // Closes this output stream and releases any system resources
    // associated with this stream. At this point, we've sent all the
    // data. Only the outputStream is closed at this point, not the
    // actual connection
    writer.close();
    // if there is a response code AND that response code is 200 OK, do
    // stuff in the first if block
    if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
        // OK

        // otherwise, if any other status code is returned, or no status
        // code is returned, do stuff in the else block
    } else {
        // Server returned HTTP error code.
    }
} catch (MalformedURLException e) {
    // ...
} catch (IOException e) {
    // ...
}

Первые 3 ответа на ваши вопросы перечислены в виде встроенных комментариев рядом с каждым методом в примере HTTP POST выше.

Из getOutputStream :

Возвращает выходной поток, который пишет в это соединение.

По сути, я думаю, что вы хорошо понимаете, как это работает, поэтому позвольте мне повторить с точки зрения непрофессионала. getOutputStreamв основном открывает поток соединения с целью записи данных на сервер. В приведенном выше примере кода «message» может быть комментарием, который мы отправляем на сервер, который представляет комментарий, оставленный в сообщении. Когда вы видите getOutputStream, вы открываете поток соединения для записи, но вы фактически не пишете никаких данных, пока не позвоните writer.write("message=" + message);.

Из getInputStream () :

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

getInputStreamделает обратное. Мол getOutputStream, он также открывает поток соединения , но цель состоит в том, чтобы читать данные с сервера, а не записывать на него. Если соединение или открытие потока не удалось, вы увидите a SocketTimeoutException.

Как насчет getInputStream? Поскольку я могу получить ответ только на getInputStream, значит ли это, что я еще не отправлял запрос на getOutputStream, а просто установил соединение?

Помните, что отправка запроса и отправка данных - это две разные операции. Когда вы вызываете getOutputStream или getInputStream url.openConnection() , вы отправляете запрос на сервер для установления соединения. Происходит рукопожатие, когда сервер отправляет вам подтверждение того, что соединение установлено. В этот момент вы готовы отправлять или получать данные. Таким образом, вам не нужно вызывать getOutputStream, чтобы установить соединение, открывающее поток, если только ваша цель не состоит в том, чтобы отправить данные.

С точки зрения непрофессионала, сделать getInputStreamзапрос равносильно тому, чтобы позвонить в дом вашего друга и сказать: «Эй, все в порядке, если я приду и одолжу эту пару тисков?» и твой друг устанавливает рукопожатие, говоря: «Конечно! Приди и возьми его». Затем, в этот момент, соединение установлено, вы идете к дому вашего друга, стучите в дверь, запрашиваете тиски и возвращаетесь в свой дом.

Используя аналогичный пример, getOutputStreamвы можете позвонить своему другу и сказать: «Эй, у меня есть те деньги, которые я вам должен, могу ли я отправить их вам»? Твой друг, которому нужны деньги и больной, чтобы ты так долго хранил его, говорит: «Конечно, давай, дешевый ублюдок». Таким образом, вы идете к дому своего друга и "ПОСТ" деньги ему. Затем он выгоняет тебя, и ты возвращаешься к себе домой.

Теперь, продолжая пример с неспециалистом, давайте рассмотрим некоторые исключения. Если вы позвонили своему другу, а его нет дома, это может быть ошибкой 500. Если вы позвонили и получили сообщение об отключении номера, потому что ваш друг устал от того, что вы постоянно занимаете деньги, эта страница 404 не найдена. Если ваш телефон мертв, потому что вы не оплатили счет, это может быть IOException. (ПРИМЕЧАНИЕ. Этот раздел может быть не на 100% правильным. Он предназначен для того, чтобы дать вам общее представление о том, что происходит с точки зрения непрофессионала.)

Вопрос № 5:

Да, вы правы, что openConnection просто создает новый объект подключения, но не устанавливает его. Соединение устанавливается, когда вы вызываете getInputStream или getOutputStream.

openConnectionсоздает новый объект подключения. От javadocs URL.openConnection :

Новое соединение открывается каждый раз, вызывая метод openConnection обработчика протокола для этого URL.

Соединение устанавливается, когда вы вызываете openConnection, а InputStream, OutputStream или оба они вызываются при их создании.

Вопрос № 6 :

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

long start = System.currentTimeMillis();
log.info("Time so far = " + new Long(System.currentTimeMillis() - start) );

// run the above example code here
log.info("Total time to send/receive data = " + new Long(System.currentTimeMillis() - start) );

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

Информацию о закрытии соединений, о которой вы не спрашивали, смотрите в Java, когда URL-соединение закрывается? ,

jmort253
источник
Здравствуй. Спасибо!!! Это было действительно подробное объяснение, и я очень ценю ваш ответ. Если я правильно понимаю ваш ответ, оба метода getOutputStream и getInputStream устанавливают соединение, если соединение еще не установлено. Если я вызову getOutputStream, то вызову getInputStream, внутренне, HTTPURLConnection больше не будет переустанавливать соединение в getInputStream, так как я уже смог установить его в getOutStream? HttpURLConnection будет повторно использовать любое соединение, которое мне удалось установить в getOutputStream в getInputStream.
Arci
Cont .: Или это устанавливает новое и отдельное соединение для getOutputStream и getInputStream? Кроме того, если я хочу получить издержки соединения, то правильное место для установки моего таймера - до и после getOutputStream. Если я хочу получить накладные расходы на чтение, то правильное место для размещения моего таймера - до и после getInputStream.
Arci
Помните, что Javadoc говорит о getInputStream и getOutputStream: Returns an output stream that writes to this connection.и Returns an input stream that reads from this open connection.. Выходной поток и входной поток отделены от соединения.
jmort253
8
Стоит отметить, что, по-видимому, объект HttpURLConnection обращается к целевому URL только в тот момент, когда ему НУЖНО это сделать. В вашем примере у вас есть потоки ввода и вывода, которые, конечно же, ничего не могут сделать, пока не будет открыто соединение. Гораздо более простой случай - операция GET, в которой вы ничего не делаете, кроме как инициализируете соединение, а затем проверяете код ответа. В этом случае соединение фактически не устанавливается до тех пор, пока не будет вызван метод getResponseCode (). В противном случае, это отличное объяснение и исследование жизненного цикла соединения!
Spanky Quigman
1
Раньше я был сбит с толку между экземпляром UrlConnection и базовым соединением Tcp / Ip / SSL, двумя разными понятиями. Первый в основном является синонимом одного HTTP-запроса страницы. Последнее, как мы надеемся, будет создано один раз, только если вы выполняете несколько запросов страниц на один и тот же сервер.
Тим Купер
17

Тим Брей представил краткий пошаговый отчет, заявив, что openConnection () не устанавливает фактическое соединение. Скорее, фактическое HTTP-соединение не устанавливается, пока вы не вызовете такие методы, как getInputStream () или getOutputStream ().

http://www.tbray.org/ongoing/When/201x/2012/01/17/HttpURLConnection

анонимный
источник
1

В какой момент HTTPURLConnection пытается установить соединение с данным URL?

На порту, указанном в URL, если есть, в противном случае 80 для HTTP и 443 для HTTPS. Я считаю, что это задокументировано.

В какой момент я могу узнать, что мне удалось успешно установить соединение?

При вызове getInputStream () или getOutputStream () или getResponseCode () без получения исключения.

Установление соединения и отправка фактического запроса выполняется за один шаг / вызов метода? Какой это метод?

Нет и нет.

Можете ли вы объяснить функцию getOutputStream и getInputStream в терминах непрофессионала?

Любой из них сначала подключается при необходимости, затем возвращает требуемый поток.

Я замечаю, что когда сервер, к которому я пытаюсь подключиться, не работает, я получаю исключение в getOutputStream. Означает ли это, что HTTPURLConnection начнет устанавливать соединение только при вызове getOutputStream? Как насчет getInputStream? Поскольку я могу получить ответ только на getInputStream, значит ли это, что я еще не отправлял запрос на getOutputStream, а просто установил соединение? HttpURLConnection возвращается на сервер, чтобы запросить ответ, когда я вызываю getInputStream?

Смотри выше.

Правильно ли мне сказать, что openConnection просто создает новый объект соединения, но еще не устанавливает соединение?

Да.

Как измерить накладные расходы на чтение и накладные расходы на соединение?

Соединение: требуется время, чтобы getInoutStream () или getOutputStream () потребовалось для возврата, в зависимости от того, что вы вызываете первым. Читайте: время от начала первого чтения до получения EOS.

Маркиз Лорн
источник
1
Я думаю, что OP имел в виду, какое соединение установлено и в какой момент мы можем узнать состояние соединения. Не URL-адрес порта подключается к. Я предполагаю, что это было направлено на openConnection () и getInoutStream () / getOutputStream () / getResponseCode (), ответ на который будет позже.
Аникет Тхакур,
1

В какой момент HTTPURLConnection пытается установить соединение с данным URL?

Стоит уточнить, есть экземпляр UrlConnection, а также базовое соединение через сокет Tcp / Ip / SSL , 2 разные концепции. Экземпляр 'UrlConnection' или 'HttpUrlConnection' является синонимом одного запроса страницы HTTP и создается при вызове url.openConnection (). Но если вы делаете несколько url.openConnection () из одного экземпляра 'url', то, если вам повезет, они будут повторно использовать один и тот же сокет Tcp / Ip и SSL-рукопожатие ... что хорошо, если вы делать много запросов страниц на один и тот же сервер, особенно хорошо, если вы используете SSL, где накладные расходы на установку сокета очень высоки.

Смотрите: HttpURLConnection реализация

Тим Купер
источник
0

Я прошел через упражнение для захвата низкоуровневого обмена пакетами и обнаружил, что сетевое соединение запускается только такими операциями, как getInputStream, getOutputStream, getResponseCode, getResponseMessage и т. Д.

Здесь происходит обмен пакетами, когда я пытаюсь написать небольшую программу для загрузки файла в Dropbox.

введите описание изображения здесь

Ниже моя игрушка программа и аннотация

    /* Create a connection LOCAL object,
     * the openConnection() function DOES NOT initiate
     * any packet exchange with the remote server.
     * 
     * The configurations only setup the LOCAL
     * connection object properties.
     */
    HttpURLConnection connection = (HttpURLConnection) dst.openConnection();
    connection.setDoOutput(true);
    connection.setRequestMethod("POST");
    ...//headers setup
    byte[] testContent = {0x32, 0x32};

    /**
     * This triggers packet exchange with the remote
     * server to create a link. But writing/flushing
     * to a output stream does not send out any data.
     * 
     * Payload are buffered locally.
     */
    try (BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream())) {
        outputStream.write(testContent);
        outputStream.flush();
    }

    /**
     * Trigger payload sending to the server.
     * Client get ALL responses (including response code,
     * message, and content payload) 
     */
    int responseCode = connection.getResponseCode();
    System.out.println(responseCode);

    /* Here no further exchange happens with remote server, since
     * the input stream content has already been buffered
     * in previous step
     */
    try (InputStream is = connection.getInputStream()) {
        Scanner scanner = new Scanner(is);
        StringBuilder stringBuilder = new StringBuilder();
        while (scanner.hasNextLine()) {
        stringBuilder.append(scanner.nextLine()).append(System.lineSeparator());
        }
    }

    /**
     * Trigger the disconnection from the server.
     */
    String responsemsg = connection.getResponseMessage();
    System.out.println(responsemsg);
    connection.disconnect();
HarryQ
источник