【问题标题】:Android: HttpURLConnection doesn't disconnectAndroid:HttpURLConnection 不会断开连接
【发布时间】:2014-07-16 06:20:02
【问题描述】:

通过在服务器上运行netstat
直到几天前:我可以看到ESTABLISHED 的连接只有大约一秒钟,然后它会从列表
现在:它保持ESTABLISHED 大约10 秒,然后进入FIN_WAIT1FIN_WAIT2

Android代码一样,服务器还是一样

有没有可能是某种 Android 更新改变了一些事情?

我真的无法解释。

我报告下面的代码。 urlConnection.disconnect() 被执行,但连接仍然在服务器上建立。

    HttpURLConnection urlConnection = null;
    System.setProperty("http.keepAlive", "false");

    try {
        URL url = new URL(stringUrl);
        urlConnection = (HttpURLConnection) url.openConnection();
        InputStream instream = new BufferedInputStream(urlConnection.getInputStream());
        ...
        instream.close();
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (urlConnection!=null) {
            urlConnection.disconnect();
        }
    }

【问题讨论】:

  • 尝试将Connection标头设置为close
  • 我试过了,不是FIN_WAIT1和FIN_WAIT2,而是进入TIME_WAIT

标签: android httpurlconnection keep-alive netstat


【解决方案1】:

输入流上的所有数据都被消耗掉的那一刻,连接会自动释放并添加到连接池中。假设连接将在不久的将来被重用,底层的套接字连接没有被释放。在finally 块中调用disconnect 是一个好习惯,因为它会在exceptions 的情况下处理连接释放。

这里是FixedLengthInputStream的read方法的实现:

@Override public int read(byte[] buffer, int offset, int count) throws IOException {
        Arrays.checkOffsetAndCount(buffer.length, offset, count);
        checkNotClosed();
        if (bytesRemaining == 0) {
            return -1;
        }
        int read = in.read(buffer, offset, Math.min(count, bytesRemaining));
        if (read == -1) {
            unexpectedEndOfInput(); // the server didn't supply the promised content length
            throw new IOException("unexpected end of stream");
        }
        bytesRemaining -= read;
        cacheWrite(buffer, offset, read);
        if (bytesRemaining == 0) {
            endOfInput(true);
        }
        return read;
    }

当 bytesRemaining 变量变为 0 时,endOfInput 被调用,这将进一步调用具有 true 参数的释放方法,这将确保连接被池化。

protected final void endOfInput(boolean reuseSocket) throws IOException {
    if (cacheRequest != null) {
            cacheBody.close();
    }
    httpEngine.release(reuseSocket);
}

这里是release 方法的实现。 最后的 if 检查确保连接是否需要关闭或添加到连接池以供重用。

public final void release(boolean reusable) {
    // If the response body comes from the cache, close it.
    if (responseBodyIn == cachedResponseBody) {
        IoUtils.closeQuietly(responseBodyIn);
    }
    if (!connectionReleased && connection != null) {
        connectionReleased = true;
        // We cannot reuse sockets that have incomplete output.
        if (requestBodyOut != null && !requestBodyOut.closed) {
            reusable = false;
        }
        // If the headers specify that the connection shouldn't be reused, don't reuse it.
        if (hasConnectionCloseHeader()) {
            reusable = false;
        }
        if (responseBodyIn instanceof UnknownLengthHttpInputStream) {
            reusable = false;
        }
        if (reusable && responseBodyIn != null) {
            // We must discard the response body before the connection can be reused.
            try {
                Streams.skipAll(responseBodyIn);
            } catch (IOException e) {
                reusable = false;
            }
        }
        if (!reusable) {
            connection.closeSocketAndStreams();
            connection = null;
        } else if (automaticallyReleaseConnectionToPool) {
            HttpConnectionPool.INSTANCE.recycle(connection);
            connection = null;
        }
    }
}

注意:我之前回答了几个与 HttpURLConnection 相关的 SO 问题,这可以帮助您理解底层实现。以下是链接:Link1Link2

【讨论】:

  • 是否最终 {} 在异常情况下被调用?
  • @jesses.co.tt 是的 finally 块在异常情况下被执行。
【解决方案2】:

根据TCP 协议的工作原理,当您关闭连接时,它不会自动从您的套接字列表中消失。

向另一方发送终止信号时,会启动一个协议(更像是一个过程),其中第一步正是你关闭连接的意图。您向另一个节点发送一个信号,这将涉及FIN_WAIT1 状态。

当用户收到该信号后,下一步是从远程端确认它。这意味着对方服务器向您发送另一个信号,表示该节点也已准备好关闭连接。那将是FIN_WAIT2 状态。

在这两个步骤之间,可能会发生远程节点尚未响应(因此未确认您要关闭连接)。那时,您将处于称为CLOSE_WAIT 的中间状态(恢复:一旦您将FIN 信号发送到远程服务器并且他们还没有响应)。

TIME_WAIT 状态意味着您在确定关闭服务器以接收一些数据包之前给服务器一些优雅的时间。您这样做是因为可能会发生连接异常,并且远程服务器可能没有收到“断开连接”消息并向您发送一些数据包。因此,当这种情况发生时,您无需在两个节点之间创建新的套接字,而是将其与处于TIME_WAIT 状态的套接字相关联,然后简单地丢弃该数据包,因为序列号可能不会被排序。

还有一些其他的状态you might see,但是按照你描述的方式,我觉得这很正常,除非你调用.disconnect()方法时,ESTABLISHED状态会持续。在这种情况下,某些事情无法按预期工作(它可能与某种重载或未优化的代码有关,这可能会使您的执行速度非常慢)。

【讨论】:

  • CLOSE_WAIT 不是 FIN_WAIT_1 和 FIN_WAIT_2 之间的“中间状态”,这并不意味着您已向对等方发送了 FIN。这意味着您已收到一个来自对等方,但尚未发出一个。
【解决方案3】:

你必须断开你打开的 UrlConnection

HttpURLConnection urlConnection = null;
System.setProperty("http.keepAlive", "false");

try {
    URL url = new URL(stringUrl);
    urlConnection = (HttpURLConnection) url.openConnection();
    InputStream instream = new BufferedInputStream(urlConnection.getInputStream());
    ...
    instream.close();
    urlConnection.disconnect(); //HERE
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (urlConnection!=null) {
        urlConnection.disconnect();
    }
}

【讨论】:

  • finally 块在正常和异常情况下都被执行。因此无需在 try 块中调用 disconnect
猜你喜欢
  • 2019-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-17
  • 2015-08-04
相关资源
最近更新 更多