Android 앱에서 서버를 부르는 부분을 맡았다. 요청할 때마다 클라이언트를 새로 만들어 썼다.
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(url);
HttpResponse res = client.execute(get);
동작했다.
Table of contents
Open Table of contents
증상 — 오래 켜 두면 느려졌다
앱을 띄우고 한참 쓰다 보면 응답이 점점 늦어졌다. 껐다 켜면 다시 빨라졌다.
처음에는 Servlet 쪽 서버가 느린 줄 알았다. 그런데 같은 시각에 브라우저로 같은 주소를 열면 바로 떴다.
연결이 쌓이고 있었다
Log 클래스로 요청 하나가 얼마나 걸리는지 찍어 봤다. 앱을 켠 직후에는 200ms 인데 20분쯤 지나면 2초가 넘었다.
adb shell netstat 으로 열린 연결을 세어 봤다. 앱이 만든 연결이 계속 쌓여 있었다. 요청이 끝나도 줄지 않았다.
코드를 보니 응답을 받고 끝내는 처리가 빠져 있었다.
HttpResponse res = client.execute(get);
String body = EntityUtils.toString(res.getEntity());
// 여기서 끝
문서를 보니 본문을 다 읽거나 연결을 반납해야 다음에 쓸 수 있다고 돼 있었다. 안 하면 그 연결이 묶여 있다.
거기다 HttpClient 를 요청마다 새로 만들면 그 안의 ClientConnectionManager 도 같이 새로 생긴다. DefaultHttpClient 는 SingleClientConnManager 를 쓰고 그것마다 소켓을 따로 잡는다.
제한에 도달
↓
다음 요청이 기다림
느려진 것이 아니라 기다리고 있었다.
하나를 공유하고 시간 제한을 뒀다
HttpClient 를 하나만 만들어 앱 전체가 같이 쓰게 했다.
public class Http {
private static HttpClient client;
public static synchronized HttpClient get() { ... }
}
기본 클라이언트는 여러 스레드에서 동시에 쓰면 안 된다. ThreadSafeClientConnManager 를 붙여 여러 Activity 가 동시에 불러도 되게 했다.
기본값도 좁았다. 한 목적지당 2 개, 전체 20 개다. 우리는 서버가 하나라 목적지도 하나여서 2 개로 묶인다. setDefaultMaxPerRoute 를 올렸다.
연결 수가 더 늘지 않았다. 20분을 써도 응답 시간이 그대로였다.
남은 것은 시간 제한이었다. 서버가 응답을 안 주면 앱이 계속 기다려 멈춘 것처럼 보였다.
연결까지 걸리는 시간 setConnectionTimeout
응답을 받는 시간 setSoTimeout
HttpConnectionParams 로 둘 다 뒀다. 앞만 넣으면 연결은 금방 되고 그 뒤 응답이 안 와도 그 제한에 안 걸린다. 둘 다 필요하다.
재시도를 어디까지 넣을지
끊긴 것이 일시적일 수 있어서 재시도를 넣으려 했다. 그런데 전부 재시도하면 안 되는 것이 있었다.
조회 → 다시 해도 같음 재시도 함
등록 → 두 번 하면 두 개 생김 재시도 안 함
공통 함수는 이게 조회인지 등록인지 모른다. 그래서 부르는 쪽이 알려주게 했다.
request(url, RETRY_OK);
request(url, NO_RETRY);
판단은 무엇을 하는지 아는 쪽에서 하고 넘긴다. 이게 맞는 방법인지는 아직 모른다.
공용 인스턴스를 언제 닫아야 하는지도 아직 안 잡혔다. Android 문서를 보니 AndroidHttpClient 는 close() 를 안 부르면 소켓과 메모리가 샌다고 적혀 있다. 안 닫고 GC 로 가면 AndroidHttpClient created and never closed 가 로그에 뜬다.
연결이 놀고 있을 때 정리하는 closeIdleConnections 와 closeExpiredConnections 도 있었다. 어느 주기로 부를지는 아직 못 정했다.
정리
- 응답을 받고 연결을 반납해야 다음에 쓸 수 있다
- 요청마다
HttpClient를 새로 만들면 연결이 쌓인다 - 오래 켜 둘수록 느려지는 형태로 나타났다
- 느려진 것이 아니라 제한에 걸려 기다린 것이다
adb shell netstat으로 열린 연결 수를 보면 확인된다- 하나를 만들어
ThreadSafeClientConnManager와 함께 공유했다 - 한 목적지당 연결 기본값이 2 라
setDefaultMaxPerRoute를 올려야 했다 - 시간 제한이 연결과 응답 둘이다. 하나만 넣으면 안 걸린다
- 재시도는 조회만 한다. 등록은 두 개가 생긴다
- 판단은 무엇을 하는지 아는 쪽에서 하고 넘긴다
- 공용 인스턴스를 언제 닫아야 하는지 아직 모른다.
close()를 안 부르면 샌다고 한다