你能解释一下HttpURLConnection连接的过程吗?

145

我正在使用HTTPURLConnection连接到一个Web服务。我知道如何使用HTTPURLConnection,但我想了解它的工作原理。基本上,我想知道以下内容:

  • HTTPURLConnection在哪个点尝试建立与给定URL的连接?
  • 我可以在哪个点知道我已成功建立连接?
  • 建立连接和发送实际请求是在一步/方法调用中完成的吗?它是什么方法?
  • 您能否通俗易懂地解释getOutputStreamgetInputStream的功能?我注意到当我正在尝试连接到的服务器宕机时,我会在getOutputStream处收到一个异常。这是否意味着只有当我调用getOutputStream时,HTTPURLConnection才会开始建立连接?getInputStream呢?由于我只能在getInputStream处获取响应,那么这是否意味着我还没有在getOutputStream处发送任何请求,而只是建立了连接?当我调用getInputStream时,HttpURLConnection会返回服务器以请求响应吗?
  • 我是否正确地说openConnection只是创建一个新的连接对象,但还没有建立任何连接?
  • 我如何测量读取开销和连接开销?
5个回答

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

在上面的HTTP POST示例中,前三个回答列为内联注释,位于每个方法旁边。从getOutputStream开始:
返回一个写入此连接的输出流。
基本上,我认为您已经很好地理解了它的工作原理,所以让我用通俗易懂的话再重申一遍。 getOutputStream 基本上打开一个流,目的是将数据写入服务器。在上面的代码示例中,“message”可以是我们发送到服务器的评论,表示对帖子的评论。当您看到getOutputStream时,您正在打开用于写入的流,但是直到调用writer.write("message=" + message);之后才实际写入任何数据。
getInputStream()开始:
返回一个从此开放连接读取的输入流。如果在数据可供读取之前读取超时到期,则可能抛出SocketTimeoutException。
getInputStream则相反。与getOutputStream一样,它也打开了一个流,但意图是从服务器读取数据,而不是写入数据。如果连接或流打开失败,您将看到SocketTimeoutException。
getInputStream怎么样?由于我只能在getInputStream中获取响应,那么这是否意味着我尚未在getOutputStream中发送任何请求,而只是建立了连接?
请记住,发送请求和发送数据是两个不同的操作。当您调用url.openConnection()时,您向服务器发送请求以建立连接。发生握手,服务器向您发送确认,表示连接已建立。然后就在这个时间点上,您准备好发送或接收数据。因此,除非您的目的是发送数据,否则不需要调用getOutputStream来打开流。

通俗易懂地讲,使用 getInputStream 请求相当于打电话给你的朋友家,询问“嘿,我可以过来借那把卡簧钳吗?” 你的朋友通过回答“当然可以!过来拿吧”建立了握手。此时连接已经建立,你走到朋友家,敲门,请求卡簧钳,然后走回自己家。

类似地,使用 getOutputStream 的例子就是你打电话给你的朋友说“嘿,我欠你的钱我有了,我能把它寄给你吗?” 你的朋友需要这笔钱,但内心深感你将钱保留了这么长时间,于是说“当然,便宜鬼,快过来吧”。所以你走到朋友家,向他“POST”了钱,然后被赶出去,走回自己家。

现在,继续使用通俗易懂的例子,让我们看一些异常情况。如果你打电话给朋友,他不在家,那可能是一个500错误。如果你拨打的号码无法接通,因为你的朋友厌倦了你总是借钱,那就是404页面未找到。如果你的手机因为没有支付账单而无法使用,那可能是一个IOException。(注意:这部分内容可能不完全正确。它旨在用通俗易懂的方式给你一个大致的了解。)

问题 #5:

是的,你是正确的,openConnection只是创建了一个新的连接对象,但并没有建立连接。当你调用getInputStream或getOutputStream时,连接才会建立。

openConnection创建了一个新的连接对象。从URL.openConnection javadocs中可以看到:

每次通过调用此URL的协议处理程序的openConnection方法来打开一个新的连接。

当你调用openConnection时,连接会建立,并且当你实例化InputStream、OutputStream或两者时,它们也会被调用。

第六个问题:

为了测量开销,我通常会在整个连接块周围包装一些非常简单的计时代码,如下所示:

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连接?

2
嗨。谢谢!!!那确实是一份详细的解释,我真的很感激你的回答。如果我正确理解了你的回答,getOutputStream和getInputStream都会在没有建立连接时建立连接。如果我调用getOutputStream,然后调用getInputStream,在内部,HTTPURLConnection将不会在getInputStream中重新建立连接,因为我已经在getOutStream中建立了连接?HttpURLConnection将在getInputStream中重用我能够在getOutputStream中建立的任何连接。 - Arci
9
值得注意的是,HttpURLConnection 对象似乎只在需要时才到达目标 URL。在您的示例中,您有输入和输出流,当然,在连接打开之前它们什么也做不了。一个更简单的情况是 GET 操作,在这种情况下,您只需初始化连接,然后检查响应代码。在这种情况下,只有在调用 getResponseCode() 方法时,连接才会真正建立。除此之外,这是对连接生命周期的非常好的解释和探索! - Spanky Quigman
1
之前我对'UrlConnection'实例和底层的Tcp/Ip/SSL连接感到困惑,这是两个不同的概念。前者基本上等同于单个HTTP页面请求。后者是希望只在您对同一服务器进行多个页面请求时创建一次的内容。 - Tim Cooper
1
connection.setDoOutput(true); 意味着 POST。同时,你必须更正你的示例代码,使用 openConnection() 是一个 NOOP(无操作),直到实际需要连接时才会抛出异常 - 参见下面的链接:openConnection 仅创建一个新的连接对象,但并不建立任何连接。至少在 Android 中,也就是 Java 6 中,除非尝试创建流、获取响应代码等,否则不会抛出任何异常。 - Mr_and_Mrs_D
1
欢迎 - 是的,在小屏幕上它确实很难读 :) 然而,在所有Java实现中,openConnection似乎并没有做太多事情。尝试使用openConnection(“不存在的URL”); sleep; disconnect; - 会发生什么?(不确定,但我猜测不会有任何异常等) - Mr_and_Mrs_D
显示剩余19条评论

18

3

HTTPURLConnection在什么情况下会尝试连接给定的URL?

如果URL中指定了端口,则连接该端口,否则对于HTTP使用80端口,对于HTTPS使用443端口。我相信这已经有文档记录了。

如何知道我已成功建立连接?

当您调用getInputStream()getOutputStream()getResponseCode()而没有抛出异常时。

建立连接和发送实际请求是在一步/方法调用中完成的吗? 是哪个方法?

不是,在任何方法中都不是。

请用通俗易懂的语言解释getOutputStream()getInputStream()的功能。

它们中的任何一个首先连接(如果需要),然后返回所需的流。

我注意到当我要连接的服务器宕机时,getOutputStream()会抛出异常。这是否意味着只有在调用getOutputStream()HTTPURLConnection才开始建立连接?那getInputStream()呢?由于我只能在getInputStream()中获取响应,那么这是否意味着我还没有在getOutputStream()中发送任何请求,仅建立了连接?当我调用getInputStream()时,HttpURLConnection是否会返回服务器请求响应?

请参见上面的回答。

我是否正确地说openConnection()只是创建一个新的连接对象,但尚未建立任何连接?

是的。

如何测量读取和连接的开销?

连接:调用您首先调用的getInputStream()getOutputStream()返回所花费的时间。读取:从开始第一次读取到获取EOS所花费的时间。


2
我认为OP的意思是在哪个点建立连接以及在哪个点我们可以了解连接状态,而不是端口url连接到哪里。我猜这是针对openConnection()和getInputStream()/getOutputStream()/getResponseCode(),答案是后者。 - Aniket Thakur
@AniketThakur 我已经详细回答了所有问题,包括他问到的“端口URL连接”,我也回答了。 - user207421

1

HTTPURLConnection在哪个点上尝试与给定URL建立连接?

值得澄清的是,这里有一个'UrlConnection'实例和底层的Tcp/Ip/SSL套接字连接两个不同的概念。 'UrlConnection'或'HttpUrlConnection'实例相当于单个HTTP页面请求,并且在调用url.openConnection()时创建。但是,如果您从一个'url'实例中执行多个url.openConnection(),则如果幸运的话,它们将重用相同的Tcp/Ip套接字和SSL握手等内容...如果您正在向同一服务器发出大量页面请求,则这非常有用,特别是如果您使用SSL,其中建立套接字的开销非常高。

请参见: HttpURLConnection implementation


1

我进行了捕获低级数据包的练习,发现网络连接只会在执行getInputStream、getOutputStream、getResponseCode、getResponseMessage等操作时触发。

以下是我尝试编写一个小程序上传文件到Dropbox时捕获的数据包交换情况。

enter image description here

以下是我的玩具程序和注释。
    /* 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();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接