1. 배경
HTTP 프로토콜은 무국적 프로토콜입니다. 즉, 각 요청은 서로 독립적입니다. 따라서 초기 구현은 모든 HTTP 요청이 TCP 소켓 연결을 열고 상호 작용이 완료된 후에 연결이 닫히게된다는 것입니다.
HTTP 프로토콜은 완전한 이중 프로토콜이므로 세 가지 핸드 셰이크와 4 개의 파도가 필요합니다. 분명히,이 디자인에서, HTTP 요청을 보낼 때마다 많은 추가 자원, 즉 연결의 설정 및 파괴가 소비됩니다.
따라서 HTTP 프로토콜도 개발되었으며 소켓 연결 멀티플렉싱은 지속적인 연결 방법을 통해 수행됩니다.
그림에서 볼 수 있습니다.
HTTP/1.0+에 대한 HTTP/1.1의 유지 및 지속적인 연결이라는 지속적인 연결의 두 가지 구현이 있습니다.
2. HTTP/1.0+의 경우 계속하십시오
1996 년 이래로 많은 HTTP/1.0 브라우저와 서버는 프로토콜, 즉 "Keep-Alive"확장 프로토콜을 확장했습니다.
이 확장 프로토콜은 1.0 "실험적 지속적인 연결"에 대한 보완으로 나타납니다. Keep-Alive는 더 이상 사용되지 않으며 최신 HTTP/1.1 사양에 설명되어 있지 않지만 많은 응용 프로그램이 계속되었습니다.
HTTP/1.0을 사용하는 클라이언트는 헤더에 "Connection : Alive Keep-Alive"를 추가하고 서버에 연결을 열도록 요청합니다. 서버 가이 연결을 열어 두려면 응답에 동일한 헤더가 포함됩니다. 응답에 "Connection : Keep-Alive"헤더가 포함되어 있지 않으면 클라이언트는 서버가 Keep-Alive를 지원하지 않으며 응답 메시지를 전송 한 후 현재 연결을 닫을 것이라고 생각합니다.
유지 보충 프로토콜을 통해 클라이언트와 서버간에 지속적인 연결이 완료되지만 여전히 몇 가지 문제가 있습니다.
3. HTTP/1.1의 지속적인 연결
HTTP/1.1은 repoy-alive를 대체하기 위해 지속적인 연결 방법을 사용합니다.
HTTP/1.1 연결은 기본적으로 지속됩니다. 명시 적으로 닫으려면 연결을 추가해야합니다. 메시지에 헤더를 닫습니다. 즉, HTTP/1.1에서는 모든 연결이 다중화됩니다.
그러나 Keep-Alive와 마찬가지로 클라이언트와 서버가 언제든지 유휴 상태로 연결할 수 있습니다. 연결을 보내지 않음 : Close는 서버가 연결이 영원히 열려 있다고 약속한다는 것을 의미하지는 않습니다.
4. httpclient에 의해 지속적인 연결을 생성하는 방법
httpclien은 연결 풀을 사용하여 홀딩 연결을 관리합니다. 동일한 TCP 링크에서 연결을 재사용 할 수 있습니다. httpclient는 연결 풀링을 통한 지속성을 연결합니다.
실제로 "풀"기술은 일반적인 디자인이며 디자인 아이디어는 복잡하지 않습니다.
모든 연결 풀에는이 아이디어가 있지만 httpclient 소스 코드를 보면 주로 두 가지 점에 중점을 둡니다.
4.1 httpclient 연결 풀 구현
Httpclient의 지속적인 연결 처리는 다음 코드에 반영 될 수 있습니다. 다음은 MainClientExec에서 연결 풀과 관련된 부품을 추출하고 다른 부분을 제거하는 것입니다.
공개 클래스 MainClientExec는 ClientExecChain {@override public closeblehttpresponse execute (최종 httproute 경로, 최종 httprequestwrapper 요청, 최종 httpclientContext context, execaware, httpexception, httpexception의 연결 요청을 얻는다. ConnectionRequest 최종 ConnectionRequest Connrequest = Connmanager.requestConnection (Route, UserToken); 최종 HTTPClientConnection ManagedConn; 최종 int timeout = config.getConnectionRequestTimeout (); // 연결 요청에서 관리 된 연결을 가져옵니다. ConnectionRequesThttPclientConnection ManagedConn = Connrequest.get (timeout> 0? timeout : 0, timeUnit.milliseconds); // 연결 관리자 제출 httpclientConnectionManager 및 관리 된 연결 httpClientConnection은 최종 연결 보유자 Connholder = New ConnectionHolder (this.log, this.connmanager, managedConn)를 보유합니다. {httpresponse 응답; if (! managedconn.isopen ()) {// 현재 관리되는 연결이 열린 상태에 있지 않으면 설정된 연결 (proxyauthstate, managedConn, Route, Request, Context)을 다시 설정해야합니다. } // 연결을 통한 요청 보내기 httpclientConnection Response = requestExecutor.Execute (request, managedConn, 컨텍스트); // 연결 재사용 전략을 통해 연결을 재사용 할 수 있는지 여부를 구별합니다. // 연결 유효성 기간을 설정합니다. // 현재 연결을 재사용 가능한 상태로 표시합니다. connholder.markReusable (); } else {connholder.marknonReusable (); }} 최종 httpentity entity = response.getentity (); if (entity == null ||! entity.isstreaming ()) {// 다음 호출에 대한 Connholder.releaseConnection ()에 대한 현재 연결을 풀에 릴리스합니다. 새로운 httpresponseproxy를 반환합니다 (응답, null); } else {return new httpresponseProxy (응답, Connholder); }}여기서 우리는 HTTP 요청 프로세스 동안 연결 처리가 프로토콜 사양과 일치한다는 것을 알 수 있습니다. 여기서는 특정 구현에 대해 논의 할 것입니다.
poolinghttpclientConnectionManager는 httpclient의 기본 연결 관리자입니다. 먼저 requestConnection ()을 통해 연결 요청을 얻습니다. 이것은 연결이 아닙니다.
Public ConnectionRequest RequestConnection (최종 Httproute Route, Final Object State) {Final Future <CpoolentRy> Future = this.pool.Lease (Route, State, NULL); return new ConnectionRequest () {@override public boolean cancel () {return future.cancel (true); } @override public httpclientConnection get (최종 긴 시간 초과, 최종 타임 유닛 튜닝)은 인터럽트 exception, executionException, ConnectionPoolTimeOutException {최종 httpclientConnection conn = leaseconnection (미래, 타임 아웃, 튜닝); if (conn.isopen ()) {최종 httphost 호스트; if (route.getProxyHost ()! = null) {host = rout.getProxyHost (); } else {host = route.getTargethost (); } Final SocketConfig SocketConfig = ResolvesocketConfig (호스트); conn.setsockettimeout (socketconfig.getSotimeout ()); } return conn; }}; }반환 된 ConnectionRequest 객체는 실제로 Connection Pool에서 관리하는 Future <cpoolentry>를 보유하는 실제 연결 인스턴스임을 알 수 있습니다.
위의 코드에서 우리는 다음에 중점을 두어야합니다.
Future<CPoolEntry> future = this.pool.lease(route, state, null)
연결 풀에서 비동기 연결을 얻는 방법 CPOOL, 미래 <CPOOLENTRY>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
미래에 비동기 연결을 통해 진정한 연결을 얻는 방법 <cpoolentry>
4.2 미래 <CPOOLENTRY>
CPOOL이 미래의 <cpoolentry>를 어떻게 출시하는지 살펴 보겠습니다. AbstractConnpool의 핵심 코드는 다음과 같습니다.
private e getPoolentRyBlocking (최종 T 경로, 최종 객체 상태, 최종 긴 타임 아웃, 최종 시간 기관 튜닝, 최종 미래 <E> 미래) IOException, InterruptedException, timeOutexception {// 현재 연결 풀을 잠그십시오. 현재 잠금 장치는 ReintrantLockthis.lock.lock ()입니다. {// 현재 httproute에 해당하는 연결 풀을 가져옵니다. HttpClient의 연결 풀의 경우, 총 풀은 크기를 가지며 각 경로에 해당하는 연결은 또한 풀이므로 "풀의 풀"최종 Routespecificpool <t, c, e> pool = getpool (route); E 입장; for (;;) {asserts.check (! this.isshutdown, "Connection Pool Shut Down"); // 경로에 해당하는 풀에서 연결을 가져 오거나 유효한 연결 항목 = pool.getFree (state); // NULL이 있으면 루프를 종료하면 (entry == null) {break; } // 연결이 만료되거나 연결이 닫힌 경우 리소스를 릴리스하고 계속 루프를 계속 사용하여 (ENTERT.ISEXPIRED (SYSTEM.CURRENTTIMEMILLIS ())) {ENTERT.CLOSE (); } if (Entry.isClosed ()) {this.available.remove (enterd); 수영장 (입력, 거짓); } else {// 유효한 연결이 발생하면 루프 중단을 종료하십시오. }} // 유효한 연결이 발생하면 (Entry! = null) {this.available.remove (Entry); this.leident.add (Entry); onreuse (입력); 반환 항목; } // 여기에서 유효한 연결이 얻어지지 않았 음을 증명하려면 최종 int maxperroute = getmax (route)를 생성해야합니다. // 각 경로에 해당하는 최대 연결 수는 구성 가능합니다. 초과하면 LRU 최종 int extess = math.max (0, pool.getallocatedCount () + 1 -MaxPerroute)를 통해 일부 연결을 정리해야합니다. if (extess> 0) {for (int i = 0; i <extess; i ++) {final e lasted = pool.getLastused (); if (lastused == null) {break; } lastused.close (); this.available.remove (lastused); 수영장. }} // 현재 경로 풀의 연결 수는 온라인에 도달하지 못했습니다. (pool.getArcatedCount () <MaxPerroute) {Final int totalused = this.leeds.size (); 최종 int freecapacity = math.max (this.maxtotal- Totalused, 0); // 연결 풀이 온라인 라인을 초과하는지 판단합니다. 그것을 초과하면 LRU를 통해 일부 연결을 정리해야합니다. if (freecapacity> 0) {Final int totalavailable = this.available.size (); // 무료 연결 수가 이미 사용 가능한 공간보다 큰 경우 (TotalAvailable> FreeCapacity -1) {if (! this.available.isempty ()) {final e thised = this.available.removelast (); lastused.close (); Final RoutEspecificpool <t, c, e> elespool = getPool (lastused.getRoute ()); Otherpool.remove (마지막으로); }} // Route Final C Conn = this.connfactory.create (Route)를 기반으로 연결을 만듭니다. //이 연결을 경로 입력 = pool.add (conn)에 해당하는 "작은 풀"에 넣습니다. //이 연결을 "큰 풀"에 넣습니다. 반환 항목; }} //이를 위해, 획득 한 경로 풀에서 유효한 연결이 없다는 것이 증명되며, 직접 연결을 설정하려면 현재 경로 연결 풀이 최대 값에 도달했지만 이미 사용 중이지만 현재 스레드 부울 성공에 사용할 수 없습니다. try {if (future.iscancelled ()) {Throw New InterruptedException ( "작동 중단"); } // 앞으로 미래를 경로 수영장에 넣습니다. 수영장을 기다리고 있습니다. //이를 기다리는 큰 연결 풀에 미래를 넣습니다. // 세마포어의 알림을 기다리면 (Deadline! = null) {success = this.condition.awaituntil (마감); } else {this.condition.await (); 성공 = 참; } if (future.iscancelled ()) {Throw New InterruptedException ( "작동 중단"); }} 마침내 {// pool.unqueue (미래)를 제거합니다. this.pending.remove (미래); } // 세마포어 알림이 기다리지 않고 현재 시간이 시간이 초과되면 (! success && (Deadline! = null && deadline.gettime) <= System.CurrentTimeMillis ())) {break; }} // 결국, 세마포어 알림이 접수되지 않았으며 사용 가능한 연결이 얻어지지 않았으며 예외가 발생했습니다. 새로운 TimeOutException ( "연결 대기 대기 대기"); } 마침내 {// 큰 연결 풀에서 잠금을 해제 this.lock.unlock (); }}위의 코드 로직에는 몇 가지 중요한 점이 있습니다.
지금 까지이 프로그램은 사용 가능한 Cpoolentry 인스턴스를 얻거나 예외를 던져 프로그램을 종료했습니다.
4.3 httpclientConnection
보호 된 httpclientConnection LeaseConnection (Final Future <cpoolentry> 미래, 최종 긴 타임 아웃, 최종 타임 유닛 튜닝)은 InterruptedException, executionException, ConnectionPoolTimeoutException {Final Cpoolentry Entry; 시도 {// 비동기식 작동 미래에서 cpoolentry 항목을 얻으십시오. if (entry == null || future.iscancelled ()) {wrach new InterruptedException (); } asserts.Check (Entry.GetConnection ()! = null, "연결 없음이있는 풀 입력"); if (this.log.isdebugenabled ()) {this.log.debug ( "Connection Spared :" + format (enterd) + formatstats (enther.getRoute ()); } // cpoolentry의 프록시 객체를 가져 오면 동일한 기본 httpclientConnection return cpoolproxy.newproxy (Entry)를 사용하여 작업이 수행됩니다. } catch (Final TimeoutException EX) {throw new ConnectionPoolTimeOutException ( "풀에서 연결 대기 대기"); }} 5. httpclient에서 지속적인 연결을 재사용하는 방법은 무엇입니까?
이전 장에서는 httpclient가 연결 풀을 통해 연결을 받고 연결을 사용해야 할 때 풀에서 가져옵니다.
세 번째 장에 해당합니다.
4 장에서, 우리는 HTTPClient가 어떻게 1과 3의 문제를 처리하는지 보았으므로 두 번째 질문을 어떻게 처리합니까?
즉, httpclient는 사용 후 연결을 닫아야하는지 또는 다른 사람들이 재사용 할 수 있도록 풀에 배치 해야하는지 어떻게 결정합니까? MainClientExec의 코드를보십시오
// http 연결 응답을 보내기 = requestExecutor.Execute (요청, managedConn, 컨텍스트); // (reusestrategy.keepalive (응답, 컨텍스트)) {// 재사용 해야하는 연결, 연결 시간 초과 시간을 기준으로 연결 시간 초과 시간을 가져 오기 (reasestrategy.keepalive (응답, 컨텍스트)) {// 연결 시간 초과 시간을 기준으로 연결 시간 초과 시간을 가져옵니다 (응답, 컨텍스트); if (this.log.isdebugenabled ()) {최종 문자열 s; // 타임 아웃은 밀리 초의 수입니다. 설정하지 않으면 -1이면 " + duration +" " + timeUnit.milliseconds의 경우 시간 초과가 없습니다. } else {s = "무기한"; } this.log.debug ( "연결은 살아남을 수 있습니다" + s); } // 타임 아웃 시간을 설정합니다. 요청이 종료되면 연결 관리자는 시간 초과 시간 Connholder.setValidfor (시간, TimeUnit.milliseconds)를 기준으로 풀에 닫을지 또는 다시 넣을지 결정합니다. // 재사용 가능한 connholder.markReusable ()로 연결을 가입합니다. } else {// 연결할 수없는 Connholder.MarkNonReusable ()로 연결을 가입합니다. }연결을 사용하여 요청이 발생한 후 연결을 재사용할지 여부를 결정하는 연결 재시도 정책이 있음을 알 수 있습니다. 재사용되면 종료 후 httpclientConnectionManager로 넘겨갑니다.
그렇다면 연결 멀티플렉싱 정책의 논리는 무엇입니까?
공개 클래스 DefaultClientConnectionReaseStrategy 확장 기본 콘센트 리우스 스트레이트 GRATEGY {public static final defaultClientConnectionReusestrategy instance = new DefaultClientConnectionReusestrategy (); @override public boolean keepAlive (최종 HTTPRESPONSE 응답, 최종 HTTPCONTEXT 컨텍스트) {// 컨텍스트에서 최종 HTTPREQUEST 요청 최종 HTTPREQUEST 요청 = (httpRequest) context.getAttribute (httpcorecontext.http_request); if (request! = null) {// 헤더 최종 헤더를 가져옵니다 [] connheaders = request.getheaders (httpheaders.connection); if (connheaders.length! = 0) {Final Tokeniterator ti = New Basictokenitorator (New Basicheaderiterator (Connheaders, NULL)); while (ti.hasnext ()) {Final String token = ti.nextToken (); // 연결 : Close 헤더가 포함 된 경우 요청이 연결을 유지하려고하지 않으며 응답 의도는 무시됩니다. 이 헤더는 http/1.1 if (http.conn_close.equalsignorecase (token)) {return false; }}}}} // 상위 클래스의 재사용 전략을 사용하여 super.keepalive (응답, 컨텍스트)를 반환합니다. }}부모 클래스의 재사용 전략을 살펴보십시오
if (CanResponseHaveBody (요청, 응답)) {Final Header [] Clhs = response.getheaders (http.content_len); // 응답의 내용 길이가 올바르게 설정되지 않으면 연결이 재사용되지 않습니다. // 지속적인 연결의 경우 두 전송 사이의 연결을 다시 설정할 필요가 없으므로 콘텐츠가 "스틱 패킷"Phenomenon을 적절히 처리하기 위해 컨텐츠 길이에 속하는 컨텐츠가 속한 내용을 확인 해야하는지 확인해야합니다. CLH = CLHS [0]; {final int contentlen = integer.parseint (clh.getValue ()); if (contentlen <0) {return false; }} catch (final numberformatexception ex) {return false; }} else {return false; }} if (headeriterator.hasnext ()) {try {Final Tokeniterator ti = new Basictokeniterator (Headeriterator); 부울 keepalive = false; while (ti.hasnext ()) {Final String token = ti.nextToken (); // 응답에 연결이있는 경우 : 닫기 헤더가 닫혀야한다고 명시 적으로 명시되어 있고 if (http.conn_close.equalsignorecase (token)) {return false; // 응답에 연결이있는 경우 : keep-alive header, 그것이 지속되어야한다고 명시 적으로 명시되어있다. }} if (reportalive) {return true; }} catch (Final ParseException px) {return false; }}} // 응답에 관련 연결 헤더 설명이없는 경우 HTTP/1.0 버전보다 높은 모든 연결이 재사용되어 재사용됩니다! ver.lessequals (httpversion.http_1_0);요약하려면 :
코드에서 볼 수 있듯이 구현 전략은 2 장 및 3 장 프로토콜 계층의 제약과 일치합니다.
6. httpclient에서 만료 된 연결을 청소하는 방법
연결 풀에서 연결을 재사용 할 때 httpclient 4.4 이전에는 만료되는지 여부를 확인하고 만료되면 청소합니다.
후속 버전은 다릅니다. 연결 풀에서 연결을 스캔하는 별도의 스레드가 있습니다. 설정된 시간의 마지막 사용이 있음을 알게 된 후에는 정리됩니다. 기본 시간 초과는 2 초입니다.
public closeablehttpclient build () {// 만료 된 유휴 연결을 정리하고 유휴 연결을 정리하려면 청소 스레드가 시작됩니다. (EvictexPiredConnections || evictIdleConnection) {// 연결 풀의 깨끗한 스레드 생성 최종 IdleConnectionEvictor ConnectionEvictor = New IdleConnectionEvictor (CM, MaxIdleTime> 0? MaxIdletime : 10, MaxIdleTimeUnit! = NULL? maxidletimeUnit); CloseAblescopy.add (new closeable () {@override public void close ()는 ioexception {connectionEvictor.shutdown (); try {ConnectionEvictor.awaitermination (1L, TimeUnit.seconds);} catch (final interruptedException) {thread.currentthread ()); // 청소 스레드를 실행합니다 ConnectionEvictor.start ();}httpclientBuilder가 구축 될 때 청소 기능이 활성화되면 연결 풀 청소 스레드가 생성되어 실행되는 것을 알 수 있습니다.
Public IdleConnectionEvictor (최종 HTTPCLIENTCONNECTIONMANAGER CONNECTIONMANAGER, 최종 THREARDFACTORY THREADFACTORY, 최종 긴 수면 시간, 최종 시간 기관인 SleepTimeUnit, 최종 Long MaxIdLeTime, 최종 TimeUnit MaxIdleTimeUnit) {this.ConnectionManager = args.NotNull (ConnectionManager, "Connection Manager"); this.threadfactory = threadfactory! = null? ThreadFactory : 새로운 DefaultThreadToflory (); this.sleeptimems = sleeptimeUnit! = null? SleepTimeUnit.tomillis (수면 시간) : 수면 시간; this.maxidletimems = maxidletimeUnit! = null? maxidletimeUnit.tomillis (maxIdletime) : maxIdletime; 이. ConnectionManager.closeexpiredconnection (); // 유휴 연결을 정리하면 (maxidletimems> 0) {ConnectionManager.closeIdleConnection (maxIdletimems, timeUnit.milliseconds); }요약하려면 :
7.이 기사의 요약
위의 연구는 HTTPClient 소스 코드에 대한 개인적인 이해를 기반으로합니다. 오류가 있으면 모든 사람이 적극적으로 논의하기 위해 메시지를 남기지 않기를 바랍니다.
좋아, 위는이 기사의 전체 내용입니다. 이 기사의 내용에 모든 사람의 연구 나 작업에 대한 특정 참조 가치가 있기를 바랍니다. 궁금한 점이 있으면 의사 소통을 위해 메시지를 남길 수 있습니다. Wulin.com을 지원 해주셔서 감사합니다.