1。背景
HTTPプロトコルはステートレスプロトコルです。つまり、各要求は互いに独立しています。したがって、その最初の実装は、すべてのHTTP要求がTCPソケット接続を開き、相互作用が完了した後に接続が閉じられることです。
HTTPプロトコルは完全な二重プロトコルであるため、確立および切断に3つの握手と4つの波が必要です。明らかに、この設計では、HTTPリクエストを送信するたびに、多くの追加リソース、つまり接続の確立と破壊を消費します。
したがって、HTTPプロトコルも開発されており、ソケット接続の多重化は永続的な接続方法を介して実行されます。
写真から、あなたは見ることができます:
持続的な接続には2つの実装があります。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を使用するクライアントは、ヘッダーに「接続:キープアライブ」を追加し、接続を開いたままにするようにサーバーに要求します。サーバーがこの接続を開いたままにしておくことをいとわない場合、応答に同じヘッダーが含まれます。応答に「接続:キープアライブ」ヘッダーが含まれていない場合、クライアントはサーバーがキープアライブをサポートせず、応答メッセージを送信した後の現在の接続を閉じると考えるでしょう。
キープアライブ補足プロトコルを通じて、クライアントとサーバーの間に永続的な接続が完了しますが、まだいくつかの問題があります。
3. HTTP/1.1の永続的な接続
HTTP/1.1は、キープアライブを置き換えるために永続的な接続方法を使用します。
HTTP/1.1接続はデフォルトで永続的です。明示的に閉じたい場合は、接続を追加する必要があります。メッセージにヘッダーを閉じます。つまり、HTTP/1.1では、すべての接続が多重化されています。
ただし、キープアライブと同様に、クライアントとサーバーがいつでも閉じることができます。接続を送信しない:閉じることは、サーバーが接続が永遠に開いたままであることを約束するという意味ではありません。
4. httpclientによって永続的な接続を生成する方法
HTTPCLIENは、接続プールを使用して保持接続を管理します。同じTCPリンクでは、接続を再利用できます。 httpclientは、接続プーリングを介して永続性を接続します。
実際、「プール」テクノロジーは一般的なデザインであり、そのデザインのアイデアは複雑ではありません。
すべての接続プールにはこのアイデアがありますが、HTTPClientソースコードを見ると、主に2つのポイントに焦点を当てます。
4.1 httpClient接続プールの実装
httpclientの永続的な接続の処理は、次のコードに反映できます。以下は、MainClientExecから接続プールに関連する部品を抽出し、他の部品を削除するためです。
パブリッククラスMainClientExecは、クライアントExecCchain {@Override public closeablehttpresponse execute(最終httprouteルート、最終的なhttpclientcontextコンテキスト、最終httpclientcontextコンテキスト、最終的なhttpexecutionaware execaware)を実装します。 ConnectionRrequest final ConnectionRequest connrequest = connmanager.RequestConnection(route、usertoken); final httpclientConnection ManagedConn; final int timeout = config.getConnectionRequestTimeout(); //接続から管理された接続を取得しますRequest ConnectionRequesthttpClientConnection ManagedConn = connrequest.get(Timeout> 0?Timeout:0、TimeUnit.MilliseConds); //接続マネージャーを送信httpclientConnectionManagerと管理された接続httpclientConnectionを接続者に送信します。 {httpresponse応答; (!managedconn.isopen()){//現在管理されている接続がオープン状態にない場合、確立された接続を再確立する必要があります(proxyauthstate、managedconn、route、request、context); } //接続を介して要求を送信httpclientConnection Response = requestExecutor.execute(request、managedconn、context); //接続を再利用することができるかどうかを区別します(Reusestrategy.KeePalive(response、context)){//接続の妥当性期間最終長さを取得= KeepAlivestrategy.getKeePaliveduration(response、context); //接続の有効性期間Connolder.setValidfor(duration、timeunit.milliseconds)を設定します。 //現在の接続を再利用可能な状態connolder.markReusable()としてマークします。 } else {connholder.markNonReusable(); }} final httpentity entity = respons.getEntity(); if(entity == null ||!entity.isstreaming()){//次の呼び出しのためにプールへの現在の接続をリリースします。新しいhttpresponseproxy(response、null)を返します。 } else {return new httpresponseproxy(response、connholder); }}ここでは、HTTP要求プロセス中の接続の処理がプロトコル仕様と一致していることがわかります。ここでは、特定の実装について説明します。
poolinghttpclientConnectionManagerは、httpclientのデフォルト接続マネージャーです。最初に、RequestConnection()を介して接続要求を取得します。これは接続ではないことに注意してください。
public ConnectionRrequest RequestConnection(最終httprouteルート、最終オブジェクト状態){final future <cpoolentry> future = this.pool.lease(route、state、null); new ConnectionRequest(){@Override public boolean cancel(){return future.cancel(true); } @Override public httpclientConnection Get(最終的なロングタイムアウト、最終TimeUnit Tunit)Sthrows arturnedexception、executionexception、connectionPoolTimeOutException {final httpclientConnection connection = leaseConnection(future、Timeout、Tunit); if(conn.isopen()){final httphost host; if(route.getProxyHost()!= null){host = route.getProxyHost(); } else {host = route.getTargethost(); } final socketconfig socketconfig = ResolvesocketConfig(host); conn.setsockettimeout(socketconfig.getSotimeout()); } return conn; }}; }返されたConnectionRequestオブジェクトは、実際には、接続プールによって管理されているFuture <CPoolentry>を保持する実際の接続インスタンスであることがわかります。
上記のコードから焦点を当てる必要があります。
Future<CPoolEntry> future = this.pool.lease(route, state, null)
接続プールから非同期接続を取得する方法cpool、future <cpoolentry>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
未来への非同期接続によって真のつながりを取得する方法<cpoolentry>
4.2未来<CPoolentry>
CPOOLが未来<CPoolentry>をどのようにリリースするかを見てみましょう。 AbstractConnpoolのコアコードは次のとおりです。
プライベートe getpoolentryblocking(最終Tルート、最終オブジェクト状態、最終ロングタイムアウト、最終タイムニットトニット、最終的な将来<e>将来)は、IoException、中断exception、timeoutexception {//現在の接続プールを最初にロックします。現在のロックは、ReentrantLockthis.Lock.Lock()です。 {//現在のhttprouteに対応する接続プールを取得します。 HTTPClientの接続プールの場合、合計プールのサイズがあり、各ルートに対応する接続もプールであるため、「プールのプール」最終RouteSpificPool <t、c、e> pool = getpool(ルート)です。 Eエントリ; for(;;){asserts.check(!this.isshutdown、 "接続プールシャットダウン"); //ルートに対応するプールから接続を取得します。これはnull、または有効な接続エントリ= pool.getfree(state); // nullを取得した場合、ループを終了しますif(entry == null){break; } //有効期限が取得された場合、または接続が閉じられている場合は、リソースをリリースし、ループを継続して(entry.isexpired(system.currenttimemillis())){entry.close(); } if(entry.isclosed()){this.abailable.remove(entry); pool.free(entry、false); } else {//有効な接続を取得した場合、ループブレークを終了します。 }} //有効な接続を取得した場合、if(entry!= null){this.abailable.remove(entry); this.lead.add(entry); onReuse(entry);戻りの入場。 } //ここで有効な接続が取得されていないことを証明するには、最終的なint maxperroute = getMax(route)を生成する必要があります。 //各ルートに対応する接続の最大数は構成可能です。それを超える場合は、lru final int expres = math.max(0、pool.getallocatedCount() + 1 -maxperroute)を介していくつかの接続をクリーンアップする必要があります。 if(過剰> 0){for(int i = 0; i <offer; i ++){final e lastused = pool.getLastused(); if(lastused == null){break; } lastused.close(); this.available.remove(lastused); pool.remove(lastused); }} //現在のルートプールの接続の数は、オンラインに到達していません。 final int freecapacity = math.max(this.maxtotal -totaluse、0); //接続プールがオンラインラインを超えているかどうかを判断します。それを超える場合は、LRUを介していくつかの接続をクリーンアップする必要がありますif(freecapacity> 0){final int totalAvailable = this.abailable.size(); //自由接続の数が残りの使用可能なスペースよりも既に大きい場合、(TotalAvailable> freecapacity -1){if(!this.available.isempty()){final e lastused = this.available.removelast(); lastused.close(); final routespificpool <t、c、e> otherpool = getpool(lastused.getroute()); otherpool.remove(lastused); }} //ルートに基づいて接続を作成します最終c conn = this.connfactory.create(route); //この接続をルートエントリ= pool.add(conn)に対応する「小さなプール」に入れます。 //この接続を「ビッグプール」に入れますthis.Lead.Add(entry);戻りの入場。 }} //この目的のために、取得したルートプールから有効な接続がないことが証明されており、自分で接続を確立する場合、現在のルート接続プールは最大値に達しました。 try {if(future.iscelled()){throw new interruptedException( "操作が中断された"); } // pool.queue(future)を待っているルートプールに未来を置きます。 //これを待っている大きな接続プールに未来を入れます。Pending.Add(Future); //セマフォの通知を待つ場合、成功は真実です(deadline!= null){success = this.condition.awaituntil(deadline); } else {this.condition.await();成功= true; } if(future.Iscancelled()){throw new interruptedException( "操作が中断された"); }}最後に{// pool.unqueue(future); this.pending.remove(future); } //セマフォ通知が待機せず、現在の時刻がタイムアウトした場合、ループは(!success &&(deadline!= null && deadline.gettime()<= system.currenttimemillis()){break; }} //最終的には、セマフォ通知が受信されず、利用可能な接続が得られなかったため、例外がスローされました。新しいTimeOutException( "Connectを待機するタイムアウト"); }最後に{//大きな接続プールでロックをリリースthis.lock.unlock(); }}上記のコードロジックにはいくつかの重要なポイントがあります。
これまでのところ、このプログラムは利用可能なcpoolentryインスタンスを取得するか、例外をスローしてプログラムを終了しました。
4.3 HTTPCLIENTCONNECTION
保護されたHTTPCLIENTCONNECTION LEASECONNECTION(Final Future <CPoolentry> Future、Final Long Timeout、Final TimeUnit Tunit)は、中断されたException、executionException、ConnectionPoolTimeOutException {final cpoolentryエントリをスローします。 try {//非同期操作からcpoolentryエントリを取得<cpoolentry> entry = future.get(Timeout、Tunit); if(entry == null || future.Iscancelled()){throw new interruptedException(); } asserts.check(entry.getConnection()!= null、 "接続なしのプールエントリ"); if(this.log.isdebugenabled()){this.log.debug( "connection spared:" + format(entry) + formatstat(entry.getRoute()); } // cpoolentryのプロキシオブジェクトを取得すると、操作はすべて同じ基礎となるhttpclientConnection return cpoolproxy.newproxy(entry)を使用して行われます。 } catch(final timeoutexception ex){新しいConnectionPoolTimeTimeOutException( "プールからの接続を待機するタイムアウト"); }} 5. httpclientで永続的な接続を再利用する方法は?
前の章では、HTTPClientが接続プールを介して接続を取得し、接続を使用する必要がある場合にプールからそれらを取得することがわかりました。
第三章に対応:
第4章では、HTTPClientが1と3の問題をどのように処理するかを見ました。それで、2番目の質問にどのように対処しますか?
つまり、HTTPClientは、使用後に接続を閉じるかどうか、または他の人が再利用するためにプールに配置する必要があるかどうかをどのように判断しますか? MainClientExecのコードを見てください
// http接続応答を送信= requestexecutor.execute(request、managedconn、context); //現在の接続が再利用戦略に基づいて再利用されるかどうかを防御する(Reasestrategy.Keepalive(response、context)){//再利用する必要がある接続、応答のタイムアウトに基づいて接続タイムアウト時間を取得= KeepAlivestrategy.getKeePaliveDuration(応答); if(this.log.isdebugenabled()){final string s; //タイムアウトはミリ秒の数であり、設定されていない場合、それは-1です。つまり、タイムアウトはありません。 } else {s = "indefinely"; } this.log.debug( "接続は生存することができます" + s); } //タイムアウト時間を設定します。リクエストが終了すると、接続マネージャーは、タイムアウトタイムCONNHOLDER.SETVALIDFOR(duration、timeUnit.milliseconds)に基づいて、閉じるか、プールに戻すかを決定します。 // reusable connholder.markReusable()として接続をサインアップします。 } else {// reusable connholder.markNonReusable()として接続をサインアップします。 }接続を使用してリクエストが発生した後、接続再生ポリシーがあり、接続が再利用されるかどうかを決定することがわかります。再利用されると、終了後にhttpclientConnectionManagerに引き渡されます。
では、接続の多重化ポリシーのロジックは何ですか?
public class defaultclientConnectionReusSestrategy extends defaultConnectionReusStrategy {public static final defaultClientConnectionReusestrategy Instance = new DefaultClientConnectionReusestrategy(); @Override public boolean KeepAlive(最終HTTPResponse応答、最終httpContextコンテキスト){//コンテキスト最終httprequest request =(httprequest)context.getTribute(httpcecontext.http_request)のコンテキストから最終httprequestを取得します。 if(request!= null){//ヘッダー最終ヘッダー[] connheaders = request.getheaders(httpheaders.connection)を取得します。 if(connheaders.length!= 0){final tokeniterator ti = new basictowniterator(new basicheaderiterator(connheaders、null)); while(ti.hasnext()){final string token = ti.nexttoken(); //接続:クローズヘッダーが含まれている場合、リクエストが接続を維持することを意図しておらず、応答の意図は無視されることを意味します。このヘッダーは、http/1.1 if(http.conn_close.equalsignorecase(token)){return false; }}}}} //親クラスの再利用戦略を使用して、super.keepalive(response、context)を返します。 }}親クラスの再利用戦略を見てください
if(canresponsehavebody(request、response)){final header [] clhs = response.getheaders(http.content_len); //応答のコンテンツ長が正しく設定されていない場合、接続が再利用されない//永続的な接続の場合、2つの送信間の接続を再確立する必要はないため、コンテンツが「スティックパケット」Phenomenonを正しく処理するためのコンテンツの長さに基づいて属するコンテンツを確認する必要があります。 = clhs [0]; try {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 basictowniterator(headeriterator); boolean keepalive = false; while(ti.hasnext()){final string token = ti.nexttoken(); //応答に接続がある場合:ヘッダーを閉じると、閉じられることと(http.conn_close.equalsignorecase(token)){return false; //応答に接続がある場合:維持ヘッダーは、それが持続することであると明示的に述べられています、それは再利用されます} else(http.conn_keep_alive.equalsignorecase(token)){keepalive = true; }} if(keepalive){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 || evictidLeconnections){//接続プールのクリーンスレッドを作成する最終的なidleconnectionevictor connectionevictor = new idleconnectionevictor(cm、maxidletime> 0?maxidletime:10、maxidletimeunit! maxidletimeunit); closeablescopy.add(new closeable(){@override public void close()throws ioexception {connectionevictor.shutdown(); try {connectionevictor.awaittermination(1l、timeunit.seconds);} catch(final arturtionedexception interrurted){swrew.currentthread(); //クリーニングスレッドConnectionEvictor.start();}を実行するhttpclientBuilderが構築されている場合、クリーニング機能が有効になっている場合、接続プールクリーニングスレッドが作成されて実行されることがわかります。
Public IdleConnectionEvictor(最終httpClientConnectionManager ConnectionManager、最終的なスレッドファクトリーファクトリー、最終的な長いスリープタイム、最終TimeUnit SleepTime、Final Long Maxidletime、Final TimeUnit MaxidletimeUnit){this.ConnectionManager = Args.Notnull(ConnectionManager、 "Connection Manager"); this.threadfactory = threadfactory!= null? ThreadFactory:new DefaultThreadFactory(); this.sleeptimems = sleeptimeunit!= null? SleepTimeUnit.Tomillis(SleepTime):SleepTime; this.maxidletimems = maxidletimeunit!= null? maxidletimeunit.tomillis(maxidletime):maxidletime; this.thread = this.threadfactory.newthread(new runnable(){@override public void run(){//デッドループ、スレッドは実行を続けます。 ConnectionManager.CloseExpiredConnection(); }要約するには:
7。この記事の概要
上記の研究は、HTTPCLIENTソースコードの個人的な理解に基づいています。エラーがある場合、誰もが積極的に議論するためにメッセージを残すことを願っています。
さて、上記はこの記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。