Чи можете ви пояснити процес з'єднання HttpURLConnection?


134

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

  • Після чого HTTPURLConnectionнамагається встановити зв’язок із вказаною URL-адресою?
  • З якого моменту я можу знати, що мені вдалося встановити зв’язок?
  • Чи встановлюється з'єднання та надсилається фактичний запит в один крок / виклик методу? Який це метод?
  • Чи можете ви пояснити функцію getOutputStreamта getInputStreamв терміні мирян? Я помічаю, що коли сервер, до якого я намагаюся підключитися, не працює, я отримую Exceptionat getOutputStream. Чи означає це, що зв’язок HTTPURLConnectionпочне встановлюватися лише тоді, коли я посилаюсь getOutputStream? Як щодо getInputStream? Оскільки я можу отримати відповідь лише на getInputStream, то чи означає це, що я ще не надсилав жодного запиту, getOutputStreamа просто встановлює зв’язок? Чи HttpURLConnectionповертайтеся до сервера, щоб запитати відповідь, коли я викликаю getInputStream?
  • Чи правильно я стверджую, що openConnectionпросто створюється новий об’єкт з'єднання, але ще не встановлено жодного зв'язку?
  • Як я можу виміряти начитані накладні та підключити накладні витрати?

Відповіді:


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в основному відкриває потік з'єднання з наміром записувати дані на сервер. У наведеному вище прикладі коду "повідомлення" може бути коментарем, який ми надсилаємо на сервер, який представляє коментар, залишений у публікації. Коли ви бачите 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-з'єднання? .


Привіт. Дякую!!! Це було дійсно детальне пояснення, і я дуже ціную вашу відповідь. Якщо я правильно зрозумів вашу відповідь, і getOutputStream, і getInputStream встановлюють з'єднання, якщо ще не встановлено з'єднання. Якщо я зателефоную на getOutputStream, то зателефонуйте getInputStream, внутрішньо, HTTPURLConnection більше не буде відновлювати з'єднання в getInputStream, оскільки мені вже вдалося встановити це в getOutStream? HttpURLConnection повторно використовуватиме будь-яке з'єднання, яке мені вдалося встановити на getOutputStream у getInputStream.
Арчі

Проти .: Або він встановлює нове та окреме з'єднання для getOutputStream та getInputStream? Крім того, якщо я хочу отримати з'єднання накладних витрат, то належне місце для розміщення мого таймера - до і після getOutputStream. Якщо я хочу отримати читання накладних витрат, то належне місце для розміщення мого таймера - до і після getInputStream.
Арчі

Згадайте, що говорить 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, 2 окремими поняттями. Перший в основному є синонімом одного запиту 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
Я думаю, що ОП означав, яка точка з'єднання встановлена ​​і в який момент ми можемо дізнатися про стан з'єднання. Не підключується URL порту. Я здогадуюсь, що це було спрямовано на openConnection () та getInoutStream () / getOutputStream () / getResponseCode (), відповідь на які пізніше.
Aniket Thakur

1

Після чого HTTPURLConnection намагається встановити з'єднання із вказаною URL-адресою?

Варто уточнити, є екземпляр 'UrlConnection', а потім є підключення Soc 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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.