为什么android HttpURLConnection没有发回FIN?

时间:2015-06-04 09:32:28

标签: java android tcp httpurlconnection keep-alive

从我的Android应用程序中,我想将数据发布到服务器并获取响应,处理它然后发回并获得另一个请求。由于在不再对流程做出响应之前是持续沟通,因此我更倾向于使用 HttpURLConnection http.keepAlive = true

我尝试重用套接字是成功的,但我遇到的问题是:

  1. 我试图从客户端(Android应用程序)发起关闭,因为如果 终止从服务器开始,然后服务器进入 TIME_WAIT 州。我不希望我的服务器进入该状态,所以我更喜欢我的客户端启动终止。但不幸的是 我找不到使用 HttpURLConnection
  2. 执行此操作的合适方法
  3. 经过几个小时的搜索,我放弃了做上述尝试 从基于服务器的启动关闭开始 keepalivetimeout ,但是当服务器发送 FIN 时,客户端只响应 ACK ,因此连接被搁置 服务器中的 FIN_WAIT_2 和代理中的 CLOSE_WAIT
  4. Packets log snippet

    源代码:

    private HttpStatus communicateWithMDMServer(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) {
            MDMLogger.error("Failed to send data to server as the URL provided is not valid "+ e.getMessage()+"\n");
            e.printStackTrace();
        } catch (IOException e) {
            MDMLogger.error("Failed to send the status to the server : IOException "+ e.getMessage()+"\n"+e.getStackTrace());
            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;
            }
            MDMLogger.error("ErrorStream Says "+responseData);
        } catch (IOException ioe) {
            MDMLogger.debug("Exception on reading ErrorStream");
        }
    }
    
    
    /**
     * API to read data from given connection and return {@code com.manageengine.mdm.framework.communication.HttpStatus}
     * 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();
        MDMLogger.info("Response Code: "+responseCode);
        InputStream inStream = connection.getInputStream();
        MDMLogger.info("Response Header: "+connection.getHeaderFields());
        String responseData = "";
        if (responseCode == HttpURLConnection.HTTP_OK) {
            responseData = readStreamAsString(inStream);
            status.setStatus(HTTP_STATUS_SUCCESS);
            status.setUrlDataBuffer(responseData);
            MDMLogger.info("communicateWithMDMServer : Response is \n"  + status.getUrlDataBuffer());
            MDMLogger.info("Successfully send the data to server and received success response ");
        }
        else {
            status.setStatus(HTTP_STATUS_FAILURE);
            MDMLogger.error("Data sent successfully but failed to get the response and the response code is : " + responseCode);
        }
        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();
    }
    

    有人可以帮忙吗?

2 个答案:

答案 0 :(得分:5)

当您使用http.keepAlive = true连接时,开始在连接池中回收并保持打开状态。即使服务器关闭了它仍在监听的连接,客户端仍然认为它可以发送数据。毕竟,服务器FIN仅表示服务器不会发送更多数据。

由于连接池的内部逻辑无法访问,因此您几乎无法控制。然而,当您使用https时,您可以打开较低层的窗口,并可以通过Socket访问所创建的HttpsURLConnection.setSSLSocketFactory(SSLSocketFactory)

您可以为默认工厂(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,则连接甚至不应该进入池中,如果您执行相反的操作,则只有在创建新连接时才会丢弃该连接。

答案 1 :(得分:0)

我发现了一些有趣的想法there

以下是关于FIN的一些消息:

这里了解调用很重要 connection.disconnent()不保证触发FIN。来自 HttpURLConnection 的文档:

  

阅读完回复正文后,应通过致电HttpURLConnection关闭disconnect()。   断开连接释放连接所拥有的资源,以便它们可以被关闭或重用。

这里强调的是:正如你从前面所看到的那样 截图,是服务器决定终止连接 因为第一个FIN是,在某些时候,而不是我们的断开连接的号召 由目的地而非来源发送。