为什么Android HttpURLConnection没有发送回FIN?

7
从我的Android应用程序中,我想要向服务器发送数据并获取响应,然后处理它,再发送并获取另一个请求。由于这是持续通信,直到没有更多的响应需要处理,我选择使用 http.keepAlive = trueHttpURLConnection
而我重复使用套接字的尝试是成功的,但我面临的问题是:
  1. 我正在尝试从客户端(Android应用)启动关闭,因为如果终止从服务器开始,那么服务器将进入TIME_WAIT状态。我不希望我的服务器进入该状态,因此我更喜欢客户端启动终止。但不幸的是,我发现没有适合使用HttpURLConnection的方法来执行此操作。
  2. 经过数小时的搜索,我放弃了上述尝试,并根据keepalivetimeout从服务器启动关闭,但当服务器发送FIN时,客户端仅回复ACK,因此连接在服务器上保持在FIN_WAIT_2状态和代理上的CLOSE_WAIT状态。

Packets log snippet

源代码:

private HttpStatus communicateWithServer(String httpUrl, String dataToSend, boolean keepAlive) {
    HttpStatus status = new HttpStatus(HTTP_STATUS_FAILURE);
    
    try {
        
        initializeConnection(httpUrl,keepAlive);
        postDataToConnection(connection, dataToSend);
        status = readDataFromConnection(connection);
        
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
        readErrorStreamAndPrint(connection);
    }
    connection.disconnect();
    return status;
}

/**
 * API to close connection, calling this will not force the connection to shutdown
 * this will work based on the Connection header set.
 * @param connection
 */
public void closeConnection(){
    if(connection != null){
        connection.disconnect();
    }
}


/**
 * Used to initialize the HttpURLConnection for the given url
 * All properties required for connection are preset here 
 * Connection Time Out : 20 Seconds
 * Connection Type     : keep alive
 * Content Type        : application/json;charset=UTF-8
 * And also All certificates will be evaluated as Valid.[ TODO will be removed soon]
 * @param httpUrl
 * @return
 * @throws MalformedURLException
 * @throws IOException
 */
private void initializeConnection(String httpUrl, boolean keepAlive) throws MalformedURLException, IOException{
    
    URL url = new URL(httpUrl);
    connection = (HttpsURLConnection) url.openConnection();
    
    connection.setConnectTimeout(20000);
    connection.setReadTimeout(20000);
    connection.setDoInput(true);
    connection.setDoOutput(true);
    connection.setRequestMethod("POST");                                                                        //NO I18N
    connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");                            //NO I18N
    connection.setRequestProperty("Connection", "Keep-Alive");      //NO I18N
}


/**
 * API to post data to given connection 
 * call to this API will close the @OutputStream
 * @param connection
 * @param data
 * @throws IOException
 */
private void postDataToConnection(URLConnection connection , String data) throws IOException{
    
    OutputStream outStream = connection.getOutputStream();
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outStream));
    writer.write(data);
    writer.flush();
    writer.close();
    outStream.close();
}

/**
 * API to read error stream and log
 * @param connection
 */
private void readErrorStreamAndPrint(URLConnection connection){
    try{
        InputStream inStream = ((HttpURLConnection) connection).getErrorStream();
        String responseData = "";
        String line;
        BufferedReader br=new BufferedReader(new InputStreamReader(inStream));
        while ((line=br.readLine()) != null) {
            responseData+=line;
        }
        
    } catch (IOException ioe) {
        
    }
}


/**
 * API to read data from given connection and return 
 * call to this API will close the @InputStream
 * @param connection
 * @return
 * @throws IOException
 */
private HttpStatus readDataFromConnection(URLConnection connection) throws IOException{
    
    HttpStatus status = new HttpStatus(HTTP_STATUS_FAILURE);
    int responseCode=((HttpURLConnection) connection).getResponseCode();
    InputStream inStream = connection.getInputStream();
    String responseData = "";
    if (responseCode == HttpURLConnection.HTTP_OK) {
        responseData = readStreamAsString(inStream);
        status.setStatus(HTTP_STATUS_SUCCESS);
        status.setUrlDataBuffer(responseData);
    }
    else {
        status.setStatus(HTTP_STATUS_FAILURE);
    }
    inStream.close();
    return status;
}

/**
 * Read the InputStream to String until EOF
 * Call to this API will not close @InputStream
 * @param inStream
 * @return
 * @throws IOException
 */
private String readStreamAsString(InputStream inStream) throws IOException{
    StringBuilder responseData = new StringBuilder();
    String line;
    BufferedReader br=new BufferedReader(new InputStreamReader(inStream));
    while ((line=br.readLine()) != null) {
        responseData.append(line);
    }
    return responseData.toString();
}

有人可以帮忙吗?


使用HttpURLConnection的适当方法是HttpURLConnection.disconnect()。但这只是一个提示。 - user207421
@EJP,我在事务结束时使用了disconnect(),但我没有发现任何区别,您能否详细说明一下。 - Vishal Santharam
2个回答

5
当你使用http.keepAlive = true时,连接会在连接池中被循环利用并保持打开状态。即使服务器关闭了连接,它仍然在监听,客户端仍然认为可以发送数据。毕竟,服务器的FIN只表示服务器不再发送更多数据。
由于连接池的内部逻辑是不可访问的,因此你几乎没有控制权。然而,当你使用https时,你可以打开一个窗口到更低层,并通过HttpsURLConnection.setSSLSocketFactory(SSLSocketFactory)获得创建的Socket的访问权限。
你可以为默认工厂(SSLSocketFactory.getDefault())创建一个包装器,允许关闭Socket。类似下面的简化方式:
public class MySSLSocketFactory extends SSLSocketFactory {

    private SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
    private Socket last;

    public void closeLastSocket() {
        if (last != null) {
            last.close();
        }
    }

    public Socket createSocket() throws IOException {
        return this.last = factory.createSocket();
    }

    ...

}

当你关闭底层套接字时,连接将不再适用于回收并将被丢弃。因此,如果你执行closeLastSocketdisconnect,该连接甚至不会进入连接池,而如果你反过来操作,则只有在创建新连接时该连接才会被丢弃。


0

我发现一些有趣的想法在那里

以下是关于FIN的摘录:

重要的是要理解,调用connection.disconnent()并不能保证触发FIN。从HttpURLConnection的文档中可以看到:

一旦读取了响应正文,应通过调用disconnect()关闭HttpURLConnection。 断开连接会释放连接持有的资源,以便它们可以关闭或重用。

这里的重点在于“可能”:正如您可以从前面的屏幕截图中看到的那样, 是服务器在某个时刻决定终止连接,而不是我们调用disconnect, 因为第一个FIN由目标发送而不是源。


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