Почему андроид HttpURLConnection не отправляет FIN?

Из моего приложения для Android я хотел отправить данные на сервер и вернуть ответ, обработать его, затем отправить обратно и получить другой запрос. Поскольку это непрерывная связь, пока не будет больше ответа на процесс, я предпочитаю идти с HttpURLConnection с http.keepAlive = true .

И моя попытка повторного использования сокета успешна, но проблемы, с которыми я сталкиваюсь, – это:

  1. Я пытаюсь инициировать Close from Client (приложение для Android), поскольку, если завершение начинается с сервера, сервер переходит в TIME_WAIT . И я не хочу, чтобы мой сервер перешел в это состояние, поэтому я предпочел, чтобы мой клиент инициировал Termination. Но, к сожалению, я не нашел подходящего способа сделать это с HttpURLConnection
  2. После нескольких часов поиска я отказался от выполнения вышеуказанной попытки и пошел с инициированием Close from Server на основе keepalivetimeout , но когда сервер отправляет FIN , клиент отвечает только ACK , из-за этого Connection удерживается на FIN_WAIT_2 на сервере и CLOSE_WAIT в агенте.

Фрагмент протокола пакетов

Исходный код:

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

Может ли кто-нибудь помочь?

Когда вы используете 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(); } ... } 

Когда вы закрываете базовый сокет, соединение не должно быть пригодным для утилизации и будет отброшено. Поэтому, если вы closeLastSocket и disconnect соединение, вы не должны даже идти в пул, и если вы делаете наоборот, соединение должно быть отброшено только при создании нового соединения.

Я нашел интересные мысли там

Вот некоторые из них об FIN:

Здесь важно понять, что вызов connection.disconnent() не гарантирует запуск FIN. Из документации HttpURLConnection :

Как только тело ответа прочитано, соединение HttpURLConnection следует закрыть, вызвав disconnect() . Отключение освобождает ресурсы, удерживаемые соединением, поэтому они могут быть закрыты или повторно использованы.

Акцент здесь делается на том, что: как вы можете видеть из предыдущего скриншота, именно сервер решил прекратить соединение в какой-то момент, а не наш вызов для отключения, так как первый FIN отправляется адресатом, а не источником.