1. Latar belakang
Protokol HTTP adalah protokol tanpa kewarganegaraan, yaitu, setiap permintaan tidak tergantung satu sama lain. Oleh karena itu, implementasinya adalah bahwa setiap permintaan HTTP akan membuka koneksi soket TCP, dan koneksi akan ditutup setelah interaksi selesai.
Protokol HTTP adalah protokol dupleks penuh, sehingga dibutuhkan tiga jabat tangan dan empat gelombang untuk membangun dan memutuskan. Jelas, dalam desain ini, setiap kali saya mengirim permintaan HTTP, ia mengkonsumsi banyak sumber daya tambahan, yaitu pendirian dan penghancuran koneksi.
Oleh karena itu, protokol HTTP juga telah dikembangkan, dan multiplexing koneksi soket dilakukan melalui metode koneksi persisten.
Dari gambar, Anda dapat melihat:
Ada dua implementasi koneksi persisten: Koneksi HTTP/1.1 yang tetap hidup dan persisten untuk HTTP/1.0+.
2. Keep-seive untuk http/1.0+
Sejak 1996, banyak browser dan server HTTP/1.0 telah memperluas protokol, yaitu, protokol ekstensi "tetap hidup".
Perhatikan bahwa protokol ekstensi ini muncul sebagai pelengkap untuk 1,0 "koneksi persisten eksperimental". Keep-Alive tidak lagi digunakan, dan tidak dijelaskan dalam spesifikasi HTTP/1.1 terbaru, tetapi banyak aplikasi terus berlanjut.
Klien yang menggunakan HTTP/1.0 Tambahkan "Koneksi: Keep-Alive" ke header, dan meminta server agar koneksi tetap terbuka. Jika server bersedia menjaga koneksi ini tetap terbuka, itu akan mencakup header yang sama dalam respons. Jika respons tidak berisi header "koneksi: tetap hidup", klien akan berpikir bahwa server tidak mendukung Keep-Alive dan akan menutup koneksi saat ini setelah mengirim pesan respons.
Melalui protokol tambahan yang tidak hidup, koneksi yang persisten diselesaikan antara klien dan server, tetapi masih ada beberapa masalah:
3. Koneksi persisten HTTP/1.1
HTTP/1.1 menggantikan Keep-Alive dengan menggunakan koneksi persisten.
Koneksi HTTP/1.1 persisten secara default. Jika Anda ingin menutup secara eksplisit, Anda perlu menambahkan koneksi: tutup header ke pesan. Yaitu, dalam http/1.1, semua koneksi multiplexed.
Namun, seperti koneksi tetap hidup dan persisten dapat ditutup oleh klien dan server kapan saja. Tidak mengirim koneksi: Tutup tidak berarti bahwa server berjanji bahwa koneksi akan tetap terbuka selamanya.
4. Cara menghasilkan koneksi persisten dengan httpclient
HttpClien menggunakan kumpulan koneksi untuk mengelola koneksi holding. Pada tautan TCP yang sama, koneksi dapat digunakan kembali. HTTPClient menghubungkan kegigihan melalui pengumpulan koneksi.
Faktanya, teknologi "Pool" adalah desain umum, dan ide desainnya tidak rumit:
Semua kumpulan koneksi memiliki ide ini, tetapi ketika kita melihat kode sumber httpclient, kami terutama fokus pada dua poin:
4.1 Implementasi HTTPClient Connection Pool
Pemrosesan koneksi persisten HTTPClient dapat tercermin dalam kode berikut. Berikut ini adalah untuk mengekstrak bagian -bagian yang terkait dengan kumpulan koneksi dari MainClientExec dan menghapus bagian lain:
public class MainClientExec implements ClientExecChain { @Override public CloseableHttpResponse execute( final HttpRoute route, final HttpRequestWrapper request, final HttpClientContext context, final HttpExecutionAware execAware) throws IOException, HttpException { //Get a connection request from the connection manager HttpClientConnectionManager ConnectionRequest Final ConnectionRequest Connrequest = ConnManager.RequestConnection (Route, Usertoken); Final HttpClientConnection ManagedConn; final int timeout = config.getConnectionRequestTimeOut (); // Dapatkan koneksi yang dikelola dari koneksi koneksi ConnectionRequestHttpClientConnection ManagedConn = ConnRequest.get (Timeout> 0? Timeout: 0, TimeUnit.Milliseconds); // Kirimkan manajer koneksi httpClientConnectionManager dan koneksi terkelola httpClientConnection ke pemegang koneksi memegang pemegang koneksi akhir connholder = pemegang koneksi baru (this.log, this.connManager, dikelola); coba {httpresponse response; if (! ManagedConn.isopen ()) {// Jika koneksi yang saat ini dikelola tidak dalam keadaan terbuka, Anda perlu membangun kembali koneksi yang ditetapkan (Proxyauthstate, ManagedConn, Route, Request, Context); } // Kirim permintaan melalui koneksi httpClientConnection response = requestExecutor.execute (request, managedConn, konteks); // Bedakan apakah koneksi dapat digunakan kembali melalui strategi penggunaan kembali koneksi jika (reusestry.keepalive (respons, konteks)) {// Dapatkan periode validitas koneksi final long durasi = KeepaliveStrate.getkeepeepaliveduration (respons, konteks); // atur periode validitas koneksi ConnHolder.setValidfor (durasi, timeunit.milliseconds); // tandai koneksi saat ini sebagai connholder state yang dapat digunakan kembali. Markreusable (); } else {connholder.marknonreusable (); }} entitas httpentity akhir = response.getEntity (); if (entity == null ||! entity.isstreaming ()) {// Lepaskan koneksi saat ini ke kumpulan untuk panggilan berikutnya ke connholder.releaseconnection (); mengembalikan httpresponseproxy baru (respons, null); } else {return httpresponseproxy baru (respons, connholder); }}Di sini kita melihat bahwa pemrosesan koneksi selama proses permintaan HTTP konsisten dengan spesifikasi protokol. Di sini kita akan membahas implementasi spesifik.
PoolingHttpClientConnectionManager adalah manajer koneksi default httpclient. Pertama, dapatkan permintaan koneksi melalui RequestConnection (). Perhatikan bahwa ini bukan koneksi.
Public ConnectionRequest RequestConnection (rute httproute akhir, status objek akhir) {Final Future <cpoolEntry> Future = this.pool.lease (rute, state, null); return new connectionRequest () {@Override public boolean cancel () {return future.cancel (true); } @Override public httpClientConnection Get (final long timeout, final timeUnit tunit) melempar interruptedException, ExecutionException, ConnectionPoolTimeOutException {httpClientConnection conn conn = leaseconnection (Future, timeout, sunit); if (conn.isopen ()) {host httphost akhir; if (route.getproxyhost ()! = null) {host = route.getProxyhost (); } else {host = route.getTargetHost (); } final socketConfig socketConfig = resolvesocketConfig (host); conn.setsocketTimeout (socketConfig.getSoTimeout ()); } return conn; }}; }Anda dapat melihat bahwa objek ConnectionRequest yang dikembalikan sebenarnya adalah instance koneksi nyata yang menahan <cpoolEntry>, yang dikelola oleh kumpulan koneksi.
Dari kode di atas kita harus fokus pada:
Future<CPoolEntry> future = this.pool.lease(route, state, null)
Cara Mendapatkan Koneksi Asynchronous dari Connection Pool Cpool, Future <cpoolentry>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
Cara Mendapatkan Koneksi Sejati Dengan Koneksi Asynchronous ke Future <cpoolentry>
4.2 Masa Depan <Cpoolentry>
Mari kita lihat bagaimana CPOOL merilis masa depan <cpoolentry>. Kode inti AbstractConnpool adalah sebagai berikut:
Private e getPoolEntryblocking (rute t akhir, status objek akhir, waktu lama akhir, final timeunit tunit, final future <e> Future) melempar ioException, interruptedException, timeoutexception {// Kunci pertama kumpulan koneksi saat ini. Kunci saat ini adalah reentrantlockthis.lock.lock (); Coba {// Dapatkan kumpulan koneksi yang sesuai dengan httproute saat ini. Untuk kumpulan koneksi httpclient, total kumpulan memiliki ukuran, dan koneksi yang sesuai dengan setiap rute juga merupakan kolam, jadi ini adalah "kolam di kolam" ruteespecificpool final <t, c, e> pool = getpool (rute); E entri; untuk (;;) {asserts.check (! this.isshutdown, "koneksi pool shut down"); // Dapatkan koneksi dari kumpulan yang sesuai dengan rute, yang mungkin nol, atau entri koneksi yang valid = pool.getFree (state); // Jika Anda mendapatkan null, keluar dari loop if (entri == null) {break; } // Jika Anda mendapatkan koneksi yang sudah kedaluwarsa atau koneksi telah ditutup, lepaskan sumber daya dan lanjutkan ke loop untuk mendapatkan if (entry.isexpired (system.currentTimeMillis ())) {entri.close (); } if (entry.isclosed ()) {this.available.remove (entri); pool.free (entri, false); } else {// Jika Anda mendapatkan koneksi yang valid, keluar dari loop break; }} // Jika Anda mendapatkan koneksi yang valid, keluarlah jika (entri! = Null) {this.available.remove (entri); this.leased.add (entri); onreuse (entri); entri kembali; } // Untuk membuktikan bahwa tidak ada koneksi yang valid yang diperoleh, Anda perlu menghasilkan int final maxperroute = getmax (rute); // Jumlah koneksi maksimum yang sesuai dengan setiap rute dapat dikonfigurasi. Jika melebihi, Anda perlu membersihkan beberapa koneksi melalui lru final int kelebihan = math.max (0, pool.getalLocatedCount () + 1 - maxperroute); if (kelebihan> 0) {untuk (int i = 0; i <kelebihan; i ++) {final e lastused = pool.getLastused (); if (lastused == null) {break; } lastused.close (); this.available.remove (lastused); pool.remove (lastused); }} // Jumlah koneksi di kumpulan rute saat ini belum mencapai Online if (pool.getAllocatedCount () <maxperroute) {final int totboleed = this.lease.size (); final int freecapacity = math.max (this.maxtotal - totpuouse, 0); // menilai apakah kumpulan koneksi melebihi jalur online. Jika melebihi, Anda perlu membersihkan beberapa koneksi melalui LRU if (freecapacity> 0) {int int totalvailable = this.available.size (); // Jika jumlah koneksi gratis sudah lebih besar dari ruang yang tersedia yang tersisa, Anda perlu membersihkan koneksi gratis jika (TotalVailable> freecapacity - 1) {if (! This.available.isempty ()) {final e lastused = this.available.removelast (); lastused.close (); final routespecificpool <t, c, e> Otherpool = getPool (lastused.getroute ()); Otherpool.remove (lastused); }} // Buat koneksi berdasarkan rute final c conn = this.connfactory.create (rute); // Masukkan koneksi ini ke "kolam kecil" yang sesuai dengan entri rute = pool.add (Conn); // Masukkan koneksi ini ke "kolam besar" this.leased.add (entri); entri kembali; }} // Untuk tujuan ini, terbukti bahwa tidak ada koneksi yang valid dari kumpulan rute yang diperoleh, dan ketika Anda ingin membuat koneksi sendiri, kumpulan koneksi rute saat ini telah mencapai nilai maksimumnya, yaitu, sudah ada koneksi yang digunakan, tetapi tidak tersedia untuk keberhasilan Boolean utas saat ini = false; coba {if (future.iscancelled ()) {lempar new interruptedException ("Operation Interrupted"); } // Masukkan masa depan ke kolam rute menunggu biliar.queue (masa depan); // Masukkan masa depan ke kolam koneksi besar menunggu this.pending.add (masa depan); // Jika Anda menunggu pemberitahuan semaphore, kesuksesan itu benar jika (tenggat waktu! = Null) {sukses = this.condition.Awaituntil (batas waktu); } else {this.condition.aWait (); Sukses = Benar; } if (future.iscancelled ()) {lempar new interruptedException ("Operation Interrupted"); }} akhirnya {// hapus pool.unqueue (masa depan); this.pending.remove (masa depan); } // Jika pemberitahuan semaphore tidak menunggu dan waktu saat ini sudah waktunya habis, loop keluar jika (! Sukses && (batas waktu! = Null && deadline.getTime () <= system.currentTimeMillis ())) {break; }} // Pada akhirnya, tidak ada pemberitahuan semaphore yang diterima dan tidak ada koneksi yang tersedia yang diperoleh, pengecualian dilemparkan. Lemparkan EXCEXCEPTION BARU ("Batas waktu menunggu koneksi"); } akhirnya {// Lepaskan kunci pada kumpulan koneksi besar this.lock.unlock (); }}Ada beberapa poin penting dalam logika kode di atas:
Sejauh ini, program ini telah memperoleh instance cpoolentry yang tersedia, atau mengakhiri program dengan melemparkan pengecualian.
4.3 httpclientConnection
LeasEconnection HTTPClientConnection yang dilindungi (masa depan akhir <cpoolentry> Masa Depan, Timeout Panjang Akhir, Final TimeUnit Tunit) melempar InterruptedException, ExecutionException, ConnectionPoolTimeOutEutException {entri cpoolentry akhir; coba {// dapatkan entri cpoolentry dari operasi asinkron Future <cpoolEntry> entri = future.get (timeout, tunit); if (entri == null || future.iscancelled ()) {lempar new interruptException (); } Assert.check (entry.getConnection ()! = Null, "Entri pool tanpa koneksi"); if (this.log.isdebugeNabled ()) {this.log.debug ("koneksi selamat:" + format (entri) + formatStats (entry.getroute ())); } // Dapatkan objek proksi cpoolentry, dan semua operasi dilakukan dengan menggunakan httpClientConnection yang mendasari yang sama dengan cpoolproxy.newproxy (entri); } catch (final timeoutexception ex) {throw connectionPoolTimeOutException baru ("Timeout menunggu koneksi dari pool"); }} 5. Bagaimana cara menggunakan kembali koneksi persisten di httpclient?
Pada bab sebelumnya, kami melihat bahwa HTTPClient mendapatkan koneksi melalui kumpulan koneksi, dan mendapatkannya dari kumpulan ketika perlu menggunakan koneksi.
Sesuai dengan bab ketiga:
Dalam Bab 4, kita melihat bagaimana httpClient menangani masalah 1 dan 3, jadi bagaimana kita menangani pertanyaan kedua?
Artinya, bagaimana HTTPClient menentukan apakah koneksi harus ditutup setelah digunakan, atau harus ditempatkan di kolam untuk digunakan kembali orang lain? Lihatlah kode MainClientExec
// Kirim HTTP Connection Response = RequestExecutor.Execute (Request, ManagedConn, Context); // Pertahankan apakah koneksi saat ini harus digunakan kembali berdasarkan strategi penggunaan kembali jika (reusestry.keepalive (respons, konteks)) {// Koneksi yang perlu digunakan kembali, dapatkan waktu batas waktu koneksi, berdasarkan batas waktu dalam respons final durasi panjang = Keepalivestategy. if (this.log.isdebugeNabled ()) {final string s; // Batas waktu adalah jumlah milidetik, jika tidak diatur, itu -1, yaitu, tidak ada batas waktu jika (durasi> 0) {s = "untuk" + durasi + "" + timeunit.milliseconds; } else {s = "tidak terbatas"; } this.log.debug ("Koneksi dapat tetap hidup" + S); } // atur waktu batas waktu. Ketika permintaan berakhir, Manajer Koneksi akan memutuskan apakah akan menutup atau memasukkannya kembali ke kumpulan berdasarkan waktu batas waktu ConnHolder.setValidfor (Durasi, TimeUnit.Milliseconds); // daftar koneksi sebagai connholder.markreusable (); } else {// daftar koneksi sebagai connholder.marknonreusable () yang tidak dapat digunakan kembali; }Dapat dilihat bahwa setelah permintaan terjadi menggunakan koneksi, ada kebijakan Ulang Koneksi untuk memutuskan apakah koneksi akan digunakan kembali. Jika digunakan kembali, itu akan diserahkan ke HTTPClientConnectionManager setelah akhir.
Jadi apa logika kebijakan multiplexing koneksi?
Public Class DefaultClientConnectionReUseStrate meluas defaultConnectionreusestry {public static finalclientConnectionReUseStrateS instance = new DefaultClientConnectionReUseStrate (); @Override Public Boolean KeepAlive (respons httpresponse akhir, konteks httpcontext akhir) {// Dapatkan permintaan httpRequest final dari konteks konteks httpRequest request = (httpRequest) context.getAttribute (httpcorecontext.http_request); if (request! = null) {// Dapatkan header header header [] connheaders = request.getHeaders (httpheaders.connection); if (connheaders.length! = 0) {final tokeniterator ti = new BatictokenIterator (baricheaderiterator baru (connheaders, null)); while (ti.hasnext ()) {final string token = ti.nextToken (); // Jika koneksi: tutup header disertakan, itu berarti bahwa permintaan tidak bermaksud untuk menjaga koneksi, dan niat respons akan diabaikan. Header ini adalah spesifikasi http/1.1 if (http.conn_close.equalsignorecase (token)) {return false; }}}}} // Gunakan strategi penggunaan kembali kelas induk untuk mengembalikan super.keepalive (respons, konteks); }}Lihatlah strategi penggunaan kembali kelas orang tua
if (canResponsehavebody (request, response)) {final header [] clhs = response.getHeaders (http.content_len); // Jika panjang konten dari respons tidak diatur dengan benar, koneksi tidak akan digunakan kembali // Karena untuk koneksi yang persisten, tidak perlu membangun kembali koneksi antara dua transmisi, Anda perlu mengkonfirmasi yang meminta konten yang dimiliki berdasarkan pada conten-length clong dengan benar-benar clh-lengeng = Paket tongkat ". CLHS [0]; coba {final int contentLen = integer.parseint (clh.getValue ()); if (contentLen <0) {return false; }} catch (final numberformatexception ex) {return false; }} else {return false; }} if (headeriterator.hasnext ()) {coba {final tokenIterator ti = new BatictokenIterator (headeriterator); Boolean Keepalive = false; while (ti.hasnext ()) {final string token = ti.nextToken (); // Jika respons memiliki koneksi: tutup header, secara eksplisit dinyatakan bahwa itu harus ditutup, dan jika (http.conn_close.equalsignorecase (token)) {return false; // Jika respons memiliki koneksi: header tetap hidup, secara eksplisit dinyatakan bahwa itu harus bertahan, itu digunakan kembali} lain jika (http.conn_keep_alive.equalsignorecase (token)) {Keepalive = true; }} if (KeepAlive) {return true; }} catch (final parseException px) {return false; }}} // Jika tidak ada deskripsi header koneksi yang relevan dalam respons, semua koneksi lebih tinggi dari versi http/1.0 akan digunakan kembali untuk kembali! Ver.LessEquals (httpversion.http_1_0);Untuk meringkas:
Seperti dapat dilihat dari kode, strategi implementasinya konsisten dengan kendala lapisan protokol Bab 2 dan Bab 3 kami.
6. Cara membersihkan koneksi yang sudah kadaluwarsa dari httpclient
Sebelum httpclient 4.4, saat menggunakan kembali koneksi dari kumpulan koneksi, itu akan memeriksa apakah itu kedaluwarsa, dan bersihkan jika kedaluwarsa.
Versi selanjutnya akan berbeda. Akan ada utas terpisah untuk memindai koneksi di kumpulan koneksi. Setelah menemukan bahwa ada penggunaan terakhir dari waktu yang telah ditetapkan, itu akan dibersihkan. Batas waktu default adalah 2 detik.
Publik closeableHttpClient build () {// Jika Anda menentukan bahwa Anda ingin membersihkan koneksi yang kedaluwarsa dan menganggur, utas pembersih akan dimulai. Defaultnya tidak dimulai jika (EvictExpiredConnections || eVictidleConnections) {// Buat utas bersih untuk koneksi kumpulan koneksi IDLECONNECTOREVICTOR CONNECTIONEVICTOR = MAXIDLECONNECTIONEVICTOR (CM, MAXIDLETIME> 0? MAXIDLETIME: 10, MAXIDLETIMEUTIT! = NULLLETIME? NULLETIMETIMET. maxidletimeunit); closeAbleScopy.Add (new closeable () {@Override public void close () melempar ioException {connectionEvictor.shutdown (); coba {connectionEvictor.AwaitTermination (1L, timeunit.current);} cross () everrread (interupricrEcception {thread.current);}. // Jalankan pembersihan connectionEvictor.start ();}Anda dapat melihat bahwa ketika httpClientBuilder sedang membangun, jika fungsi pembersihan diaktifkan ditentukan, utas pembersihan kolam koneksi akan dibuat dan menjalankannya.
IdleconnectionEvictor publik (httpClientConnectionManager ConnectionManager akhir, ThreadFactory ThreadFactory akhir, waktu tidur panjang akhir, timeunit terakhir sleepTimeunit, 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 (waktu tidur): waktu tidur; this.maxidletimems = maxidletimeunit! = null? maxidletimeunit.tomillis (maxidletime): maxidletime; this.thread = this.threadfactory.newthread (runnable baru () {@Override public void run () {coba {// loop dead, utas terus mengeksekusi while (! Thread.currentThread (). Terpencil () {// dieksekusi setelah beberapa detik, default (). Terpencil ()) {// dieksekusi setelah beberapa detik istirahat, default 10 detik. ConnectionManager.CloseExpiredConnections (); }Untuk meringkas:
7. Ringkasan artikel ini
Penelitian di atas didasarkan pada pemahaman pribadi tentang kode sumber HTTPClient. Jika ada kesalahan, saya harap semua orang akan meninggalkan pesan untuk membahasnya secara aktif.
Oke, di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.