Können Sie den Verbindungsprozess von HttpURLConnection erklären?

134

Ich verwende HTTPURLConnection, um eine Verbindung zu einem Webdienst herzustellen. Ich weiß, wie man es benutzt, HTTPURLConnectionaber ich möchte verstehen, wie es funktioniert. Grundsätzlich möchte ich Folgendes wissen:

  • An welchem ​​Punkt wird HTTPURLConnectionversucht, eine Verbindung zur angegebenen URL herzustellen?
  • An welchem ​​Punkt kann ich wissen, dass ich erfolgreich eine Verbindung herstellen konnte?
  • Wird eine Verbindung hergestellt und die eigentliche Anforderung in einem Schritt / Methodenaufruf gesendet? Welche Methode ist das?
  • Können Sie die Funktion von getOutputStreamund getInputStreamin Laienbegriffen erklären ? Ich stelle fest, dass ich einen Exceptionat bekomme, wenn der Server, zu dem ich eine Verbindung herstellen möchte, ausgefallen ist getOutputStream. Bedeutet das, dass HTTPURLConnectionerst beim Aufrufen eine Verbindung hergestellt wird getOutputStream? Wie wäre es mit dem getInputStream? Bedeutet das getInputStreamdann, dass ich noch keine Anfrage getOutputStreamgesendet habe, sondern einfach eine Verbindung herstelle , da ich die Antwort nur bei erhalten kann ? Gehen HttpURLConnectionSie zurück zum Server, um beim Aufrufen eine Antwort anzufordern getInputStream?
  • Kann ich zu Recht sagen, dass openConnectioneinfach ein neues Verbindungsobjekt erstellt wird, aber noch keine Verbindung hergestellt wird?
  • Wie kann ich den Lese-Overhead messen und den Verbindungs-Overhead verbinden?
Arci
quelle

Antworten:

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) {
    // ...
}

Die ersten drei Antworten auf Ihre Fragen werden im obigen Beispiel-HTTP-POST neben jeder Methode als Inline-Kommentare aufgeführt.

Von getOutputStream :

Gibt einen Ausgabestream zurück, der in diese Verbindung schreibt.

Grundsätzlich denke ich, dass Sie ein gutes Verständnis dafür haben, wie dies funktioniert. Lassen Sie mich dies nur in Laienbegriffen wiederholen. getOutputStreamöffnet im Grunde einen Verbindungsstrom mit der Absicht, Daten auf den Server zu schreiben. Im obigen Codebeispiel könnte "Nachricht" ein Kommentar sein, den wir an den Server senden und der einen Kommentar darstellt, der in einem Beitrag hinterlassen wurde. Wenn Sie sehen getOutputStream, öffnen Sie den Verbindungsstrom zum Schreiben, schreiben jedoch erst dann Daten, wenn Sie anrufen writer.write("message=" + message);.

Von getInputStream () :

Gibt einen Eingabestream zurück, der von dieser offenen Verbindung liest. Eine SocketTimeoutException kann beim Lesen aus dem zurückgegebenen Eingabestream ausgelöst werden, wenn das Lesezeitlimit abläuft, bevor Daten zum Lesen verfügbar sind.

getInputStreammacht das Gegenteil. Wie getOutputStreames eröffnet auch eine Verbindung Strom, aber die Absicht ist , Daten vom Server zu lesen, nicht zu schreiben. Wenn die Verbindung oder das Öffnen des Streams fehlschlägt, wird a angezeigt SocketTimeoutException.

Wie wäre es mit dem getInputStream? Da ich die Antwort nur bei getInputStream erhalten kann, bedeutet dies, dass ich noch keine Anfrage bei getOutputStream gesendet habe, sondern einfach eine Verbindung hergestellt habe?

Beachten Sie, dass das Senden einer Anfrage und das Senden von Daten zwei verschiedene Vorgänge sind. Wenn Sie getOutputStream oder getInputStream aufrufen url.openConnection() , senden Sie eine Anforderung an den Server, um eine Verbindung herzustellen. Es tritt ein Handshake auf, bei dem der Server eine Bestätigung an Sie zurücksendet, dass die Verbindung hergestellt wurde. Zu diesem Zeitpunkt sind Sie dann bereit, Daten zu senden oder zu empfangen. Daher müssen Sie getOutputStream nicht aufrufen, um eine Verbindung zum Öffnen eines Streams herzustellen , es sei denn , Sie möchten Daten senden, um Daten anzufordern.

Für Laien ist eine getInputStreamAnfrage gleichbedeutend mit einem Anruf beim Haus Ihres Freundes, um zu sagen: "Hey, ist es in Ordnung, wenn ich vorbeikomme und mir diese beiden Schraubstöcke ausleihe?" und dein Freund stellt den Händedruck her, indem er sagt: "Sicher! Komm und hol ihn dir". Dann, an diesem Punkt, wird die Verbindung hergestellt, Sie gehen zum Haus Ihres Freundes, klopfen an die Tür, fordern die Schraubstöcke an und gehen zurück zu Ihrem Haus.

Wenn Sie ein ähnliches Beispiel für verwenden getOutputStream, rufen Sie Ihren Freund an und sagen: "Hey, ich habe das Geld, das ich Ihnen schulde. Kann ich es Ihnen schicken?" Ihr Freund, der Geld braucht und krank ist, dass Sie es so lange aufbewahrt haben, sagt: "Sicher, kommen Sie über Ihren billigen Bastard hinweg." Also gehst du zum Haus deines Freundes und "postest" das Geld an ihn. Dann tritt er dich raus und du gehst zurück zu deinem Haus.

Lassen Sie uns nun mit dem Beispiel des Laien einige Ausnahmen betrachten. Wenn Sie Ihren Freund angerufen haben und er nicht zu Hause war, könnte dies ein Fehler von 500 sein. Wenn Sie angerufen haben und eine Nachricht mit nicht verbundener Nummer erhalten haben, weil Ihr Freund es satt hat, ständig Geld zu leihen, wurde diese 404-Seite nicht gefunden. Wenn Ihr Telefon tot ist, weil Sie die Rechnung nicht bezahlt haben, kann dies eine IOException sein. (HINWEIS: Dieser Abschnitt ist möglicherweise nicht 100% korrekt. Er soll Ihnen eine allgemeine Vorstellung davon geben, was in Laienbegriffen geschieht.)

Frage 5:

Ja, Sie haben Recht, dass openConnection einfach ein neues Verbindungsobjekt erstellt, es jedoch nicht herstellt. Die Verbindung wird hergestellt, wenn Sie entweder getInputStream oder getOutputStream aufrufen.

openConnectionerstellt ein neues Verbindungsobjekt. Aus den URL.openConnection-Javadocs :

Eine neue Verbindung wird jedes Mal geöffnet, indem die openConnection-Methode des Protokollhandlers für diese URL aufgerufen wird.

Die Verbindung wird hergestellt, wenn Sie openConnection aufrufen, und InputStream, OutputStream oder beide werden aufgerufen, wenn Sie sie instanziieren.

Frage 6 :

Um den Overhead zu messen, wickle ich im Allgemeinen einen sehr einfachen Timing-Code um den gesamten Verbindungsblock, wie folgt:

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) );

Ich bin sicher, dass es fortgeschrittenere Methoden zum Messen der Anforderungszeit und des Overheads gibt, aber dies ist im Allgemeinen für meine Anforderungen ausreichend.

Informationen zum Schließen von Verbindungen, nach denen Sie nicht gefragt haben, finden Sie unter In Java, wann wird eine URL-Verbindung geschlossen? .

jmort253
quelle
Hallo. Vielen Dank!!! Das war in der Tat eine ausführliche Erklärung und ich schätze Ihre Antwort sehr. Wenn ich Ihre Antwort richtig verstehe, stellen sowohl getOutputStream als auch getInputStream eine Verbindung her, wenn noch keine Verbindung hergestellt wurde. Wenn ich getOutputStream aufrufe und dann getInputStream aufrufe, stellt HTTPURLConnection keine Verbindung mehr bei getInputStream wieder her, da ich sie bereits bei getOutStream herstellen konnte. HttpURLConnection verwendet jede Verbindung, die ich bei getOutputStream in getInputStream herstellen konnte, wieder.
Arci
Fortsetzung: Oder wird eine neue und separate Verbindung für getOutputStream und getInputStream hergestellt? Wenn ich den Verbindungsaufwand erhalten möchte, ist der richtige Ort, um meinen Timer zu setzen, vor und nach getOutputStream. Wenn ich den Lese-Overhead erhalten möchte, ist der richtige Ort, um meinen Timer zu setzen, vor und nach getInputStream.
Arci
Denken Sie daran, was der Javadoc über getInputStream und getOutputStream sagt: Returns an output stream that writes to this connection.und Returns an input stream that reads from this open connection.. Der Ausgangsstrom und der Eingangsstrom sind von der Verbindung getrennt.
jmort253
8
Es ist erwähnenswert, dass das HttpURLConnection-Objekt anscheinend nur an dem Punkt die Ziel-URL erreicht, an dem es dies tun muss. In Ihrem Beispiel haben Sie Eingabe- und Ausgabestreams, die natürlich nichts tun können, bis die Verbindung geöffnet ist. Ein viel einfacherer Fall ist eine GET-Operation, bei der Sie lediglich die Verbindung initialisieren und dann den Antwortcode überprüfen. In diesem Fall wird die Verbindung erst hergestellt, wenn die Methode getResponseCode () aufgerufen wird. Ansonsten ist dies eine großartige Erklärung und Erkundung des Verbindungslebenszyklus!
Spanky Quigman
1
Ich war zuvor verwirrt zwischen der 'UrlConnection'-Instanz und der zugrunde liegenden Tcp / Ip / SSL-Verbindung, zwei getrennten Konzepten. Ersteres ist im Grunde gleichbedeutend mit einer einzelnen HTTP-Seitenanforderung. Letzteres wird hoffentlich nur einmal erstellt, wenn Sie mehrere Seitenanforderungen an denselben Server senden.
Tim Cooper
17

Tim Bray präsentierte Schritt für Schritt eine kurze Beschreibung, dass openConnection () keine tatsächliche Verbindung herstellt. Eine tatsächliche HTTP-Verbindung wird erst hergestellt, wenn Sie Methoden wie getInputStream () oder getOutputStream () aufrufen.

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

anonym
quelle
1

An welchem ​​Punkt versucht HTTPURLConnection, eine Verbindung zur angegebenen URL herzustellen?

Auf dem in der URL angegebenen Port, falls vorhanden, andernfalls 80 für HTTP und 443 für HTTPS. Ich glaube das ist dokumentiert.

An welchem ​​Punkt kann ich wissen, dass ich erfolgreich eine Verbindung herstellen konnte?

Wenn Sie getInputStream () oder getOutputStream () oder getResponseCode () aufrufen, ohne eine Ausnahme zu erhalten.

Wird eine Verbindung hergestellt und die eigentliche Anforderung in einem Schritt / Methodenaufruf gesendet? Welche Methode ist das?

Nein und keine.

Können Sie die Funktion von getOutputStream und getInputStream in Laienbegriffen erklären?

Einer von ihnen stellt bei Bedarf zuerst eine Verbindung her und gibt dann den erforderlichen Stream zurück.

Ich stelle fest, dass bei einem Ausfall des Servers, zu dem ich eine Verbindung herstellen möchte, eine Ausnahme bei getOutputStream angezeigt wird. Bedeutet dies, dass HTTPURLConnection erst dann eine Verbindung herstellt, wenn ich getOutputStream aufrufe? Wie wäre es mit dem getInputStream? Da ich die Antwort nur bei getInputStream erhalten kann, bedeutet dies, dass ich noch keine Anfrage bei getOutputStream gesendet habe, sondern einfach eine Verbindung hergestellt habe? Geht HttpURLConnection zum Server zurück, um eine Antwort anzufordern, wenn ich getInputStream aufrufe?

Siehe oben.

Kann ich zu Recht sagen, dass openConnection einfach ein neues Verbindungsobjekt erstellt, aber noch keine Verbindung herstellt?

Ja.

Wie kann ich den Lese-Overhead messen und den Verbindungs-Overhead verbinden?

Verbinden: Nehmen Sie sich die Zeit, die getInoutStream () oder getOutputStream () benötigt, um zurückzukehren, je nachdem, was Sie zuerst aufrufen. Lesen: Zeit vom ersten Lesen bis zum Abrufen der EOS.

Marquis von Lorne
quelle
1
Ich denke, OP bedeutete, welcher Punkt verbunden ist und an welchem ​​Punkt wir den Verbindungsstatus kennenlernen können. Nicht die Port-URL stellt eine Verbindung her. Ich vermute, dies war auf openConnection () und getInoutStream () / getOutputStream () / getResponseCode () gerichtet, deren Antwort später erfolgt.
Aniket Thakur
1

An welchem ​​Punkt versucht HTTPURLConnection, eine Verbindung zur angegebenen URL herzustellen?

Es lohnt sich zu klären, es gibt die 'UrlConnection'-Instanz und dann die zugrunde liegende Tcp / Ip / SSL-Socket-Verbindung , zwei verschiedene Konzepte. Die Instanz 'UrlConnection' oder 'HttpUrlConnection' ist gleichbedeutend mit einer einzelnen HTTP-Seitenanforderung und wird erstellt, wenn Sie url.openConnection () aufrufen. Wenn Sie jedoch mehrere url.openConnection () von einer URL-Instanz aus ausführen, werden sie mit etwas Glück denselben Tcp / Ip-Socket und dasselbe SSL-Handshake-Material wiederverwenden ... was gut ist, wenn Sie es sind Viele Seitenanforderungen an denselben Server senden, besonders gut, wenn Sie SSL verwenden, bei dem der Aufwand für die Einrichtung des Sockets sehr hoch ist.

Siehe: Implementierung von HttpURLConnection

Tim Cooper
quelle
0

Ich habe die Übung zum Erfassen des Paketaustauschs auf niedriger Ebene durchlaufen und festgestellt, dass die Netzwerkverbindung nur durch Vorgänge wie getInputStream, getOutputStream, getResponseCode, getResponseMessage usw. ausgelöst wird.

Hier ist der Paketaustausch erfasst, wenn ich versuche, ein kleines Programm zum Hochladen von Dateien in Dropbox zu schreiben.

Geben Sie hier die Bildbeschreibung ein

Unten ist mein Spielzeugprogramm und meine Anmerkung

    /* 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
quelle