1. الخلفية
بروتوكول HTTP هو بروتوكول عديمي الجنسية ، أي أن كل طلب مستقل عن بعضها البعض. لذلك ، فإن تنفيذها الأولي هو أن كل طلب HTTP سيفتح اتصال مقبس TCP ، وسيتم إغلاق الاتصال بعد اكتمال التفاعل.
بروتوكول HTTP هو بروتوكول مزدوج كامل ، لذلك يتطلب ثلاثة مصافحة وأربعة موجات لإنشاء وقطع الاتصال. من الواضح ، في هذا التصميم ، في كل مرة أرسل فيها طلب HTTP ، يستهلك الكثير من الموارد الإضافية ، أي إنشاء وتدمير الاتصال.
لذلك ، تم أيضًا تطوير بروتوكول HTTP ، ويتم إجراء تعدد الإرسال على التوصيل من خلال طرق اتصال مستمرة.
من الصورة ، يمكنك أن ترى:
هناك تطبيقان للاتصالات المستمرة: اتصالات حميمة ومستمرة لـ HTTP/1.1 لـ HTTP/1.0+.
2. الحفاظ على HTTP/1.0+
منذ عام 1996 ، مددت العديد من متصفحات وخوادم HTTP/1.0 البروتوكول ، أي بروتوكول التمديد "الاحتفاظ".
لاحظ أن بروتوكول التمديد هذا يظهر كمكمل لـ 1.0 "اتصال مستمر تجريبي". لم يعد استخدام Atk-Alive ، ولم يتم شرحه في أحدث مواصفات HTTP/1.1 ، ولكن استمرت العديد من التطبيقات.
إضافة العملاء الذين يستخدمون HTTP/1.0 إضافة "اتصال: حافظ على الحافة" إلى الرأس ، وطلب الخادم للحفاظ على اتصال مفتوح. إذا كان الخادم على استعداد لإبقاء هذا الاتصال مفتوحًا ، فسيتضمن نفس الرأس في الاستجابة. إذا كانت الاستجابة لا تحتوي على رأس "الاتصال: الحفاظ على" ، فسوف يعتقد العميل أن الخادم لا يدعم الحفاظ عليه وسيغلق الاتصال الحالي بعد إرسال رسالة الاستجابة.
من خلال البروتوكول التكميلي المحافظ ، يتم الانتهاء من اتصال مستمر بين العميل والخادم ، ولكن لا تزال هناك بعض المشكلات:
3. الاتصال المستمر لـ HTTP/1.1
يأخذ HTTP/1.1 طريقة اتصال مستمرة لاستبدال الحافة.
توصيلات HTTP/1.1 مستمرة بشكل افتراضي. إذا كنت ترغب في الإغلاق بشكل صريح ، فأنت بحاجة إلى إضافة الاتصال: أغلق رأسًا على الرسالة. وهذا هو ، في HTTP/1.1 ، جميع الاتصالات تعدد الإرسال.
ومع ذلك ، مثل Keep-Alive ، يمكن إغلاق الاتصالات المستمرة الخمول من قبل العميل والخادم في أي وقت. عدم إرسال الاتصال: لا يعني إغلاق أن الخادم يعد بأن يظل الاتصال مفتوحًا إلى الأبد.
4. كيفية توليد اتصالات مستمرة بواسطة httpclient
يستخدم HTTPClien تجمع اتصال لإدارة اتصالات الحجز. على نفس رابط TCP ، يمكن إعادة استخدام الاتصالات. HttpClient يربط الثبات من خلال تجميع الاتصال.
في الواقع ، فإن تقنية "البلياردو" هي تصميم عام ، وفكرة التصميم الخاصة بها ليست معقدة:
تحتوي جميع تجمعات الاتصال على هذه الفكرة ، ولكن عندما ننظر إلى رمز مصدر HTTPClient ، فإننا نركز بشكل أساسي على نقطتين:
4.1 تنفيذ مجموعة اتصال HTTPCLIENT
يمكن أن تنعكس معالجة HTTPClient للاتصالات المستمرة في الكود التالي. فيما يلي استخراج الأجزاء المتعلقة بتجمع الاتصال من MainClientExec وإزالة الأجزاء الأخرى:
الطبقة العامة mainclientexec تنفذ clientexecchain {Override public clovablehttpresponse تنفيذ (طريق httproute النهائي ، httprequestwrapper النهائي ، طلب httpclienttxt النهائي ، الحصول على اتصال httpexecutionaware execaware. ConnectionRequest Final ConnectionRequest connrequest = connmanager.requestConnection (Route ، UserToken) ؛ Final HttpClientConnection ManagedConn ؛ نهائي int timeout = config.getConnectionRequestTimeout () ؛ // احصل على اتصال مُدار من طلب الاتصال ConnectionRequestTtpClientConnection 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 ، المسار ، الطلب ، السياق) ؛ } // إرسال طلب من خلال الاتصال httpclientconnection = requestExecutor.execute (request ، managedConn ، Context) ؛ // التمييز بين ما إذا كان يمكن إعادة استخدام الاتصال من خلال استراتيجية إعادة استخدام الاتصال إذا (reusestrategy.keepalive (الاستجابة ، السياق)) {// احصل على فترة صحة الاتصال الطويلة = keepalivestrategy.getKeepalivation (الاستجابة ، السياق) ؛ // قم بتعيين فترة صلاحية الاتصال connholder.setValidfor (المدة ، timeUnit.milliseconds) ؛ // وضع علامة على الاتصال الحالي كدولة قابلة لإعادة الاستخدام connholder.markReusable () ؛ } else {connholder.markNonreusable () ؛ }} النهائي httpentity unitity = response.getentity () ؛ if (ectity == null ||! entity.isstreaming ()) {// قم بإطلاق الاتصال الحالي إلى التجمع للمكالمة التالية إلى connholder.releaseconnection () ؛ إرجاع httpresponsproxy جديد (استجابة ، فارغة) ؛ } آخر {return new httpresponsproxy (response ، connholder) ؛ }}نرى هنا أن معالجة الاتصال أثناء عملية طلب HTTP تتفق مع مواصفات البروتوكول. سنناقش هنا التنفيذ المحدد.
PoolingHttpClientConnectionManager هو مدير الاتصال الافتراضي لـ HTTPClient. أولاً ، الحصول على طلب اتصال من خلال requestConnection (). لاحظ أن هذا ليس اتصالًا.
ConnectionRequest 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 (timeout fong ، timeunit tup) النهائي) يلقي InterruptedException ، ExecutionException ، connectionPooltimeOtexception {Final httpclientconnection conn = leaseconnection (المستقبل ، الوقت ، النعمة) ؛ if (conn.isopen ()) {final httphost host ؛ if (route.getProxyHost ()! = null) {host = route.getProxyHost () ؛ } آخر {host = route.getTarGethost () ؛ } Final SocketConfig SocketConfig = releCtOcketConfig (Host) ؛ Conn.SetSockettimeOut (SocketConfig.getSotimeout ()) ؛ } return conn ؛ }} ؛ }يمكنك أن ترى أن كائن ConnectionRequest الذي تم إرجاعه هو في الواقع مثيل اتصال حقيقي يحتفظ بـ 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 النهائي ، حالة الكائن النهائي ، الوقت النهائي ، TimeUnit Tunit ، المستقبل النهائي <E> المستقبل) يلقي IoException ، InterruptedException ، TimeoutException {// First Lock the Connection Pool. القفل الحالي هو reentrantlockthis.lock.lock () ؛ جرب {// احصل على تجمع اتصال يتوافق مع httproute الحالي. بالنسبة لمجموعة اتصال HTTPClient ، فإن تجمع الكلي له حجم ، والاتصال المقابل لكل مسار هو أيضًا تجمع ، لذلك فهو عبارة عن "تجمع في البركة" النهائي RoutSeSpecificPool <T ، C ، E> pool = getPool (Route) ؛ د الدخول ؛ لـ (؛؛) {asserts.check (! this.isshutdown ، "Connection Pool lear down") ؛ // احصل على اتصالات من التجمع المقابل للطريق ، والذي قد يكون فارغًا ، أو إدخال اتصال صالح = pool.getFree (State) ؛ // إذا حصلت على NULL ، فقم بالخروج من الحلقة if (entry == null) {break ؛ } // إذا حصلت على اتصال منتهية الصلاحية أو تم إغلاق الاتصال ، فاحرص على المورد واستمر في الحصول على حلقة للحصول على إذا (intrad.isexpired (System.CurrentTimeMillis ())) {intpl.close () ؛ } if (enter.isclosed ()) {this.available.remove (entry) ؛ pool.free (الدخول ، خطأ) ؛ } آخر {// إذا حصلت على اتصال صالح ، فقم بالخروج من كسر الحلقة ؛ }} // إذا حصلت على اتصال صالح ، فقم بالخروج إذا (إدخال! = null) {this.available.remove (entry) ؛ this.led.add (الدخول) ؛ onreuse (الدخول) ؛ إرجاع إدخال } // إلى هنا تثبت أنه لم يتم الحصول على أي اتصال صحيح ، تحتاج إلى إنشاء int maxperroute النهائي = getMax (Route) ؛ // الحد الأقصى لعدد الاتصالات المقابلة لكل مسار قابل للتكوين. إذا تجاوزت ذلك ، فأنت بحاجة إلى تنظيف بعض الاتصالات من خلال LRU Final int forp = math.max (0 ، pool.getAllocatedCount () + 1 - maxperroute) ؛ if (الزائدة> 0) {for (int i = 0 ؛ i <vergle ؛ i ++) {final e lastused = pool.getLastused () ؛ if (lastused == null) {break ؛ } lastused.close () ؛ this.available.remove (Last usered) ؛ pool.remove (Lastused) ؛ }} // لم يتم الوصول إلى عدد الاتصالات الموجودة في تجمع المسار الحالي عبر الإنترنت إذا (pool.getAllocatedCount () <maxperroute) {Final int totalused = th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th th this. Final int freeCapacity = Math.Max (this.maxtotal - totalused ، 0) ؛ // احكم على ما إذا كان تجمع الاتصال يتجاوز الخط عبر الإنترنت. إذا تجاوز ذلك ، فأنت بحاجة إلى تنظيف بعض الاتصالات من خلال LRU if (freeCapacity> 0) {Final int totalavailable = this.available.size () ؛ // إذا كان عدد الاتصالات المجانية أكبر بالفعل من المساحة المتاحة المتبقية ، فأنت بحاجة إلى تنظيف الاتصال المجاني إذا (totalavailable> freeCapacity - 1) {if (! lastused.close () ؛ Final RouteSpecificPool <T ، C ، E> OtherPool = getPool (lastused.getRoute ()) ؛ othersepool.remove (Lastused) ؛ }} // إنشاء اتصال يعتمد على الطريق النهائي c conn = this.connfactory.create (Route) ؛ // ضع هذا الاتصال في "البركة الصغيرة" المقابلة لإدخال الطريق = pool.add (conn) ؛ // ضع هذا الاتصال في "big pool" this.leved.add (الدخول) ؛ إرجاع إدخال }} // إلى هذه النهاية ، ثبت أنه لا يوجد اتصال صحيح من تجمع المسار الذي تم الحصول عليه ، وعندما تريد إنشاء اتصال بنفسك ، وصل تجمع اتصال الطريق الحالي إلى أقصى قيمته ، أي وجود اتصال بالفعل ، لكنه غير متوفر لنجاح Boolean الحالي = خطأ ؛ حاول {if (future.iscancelled ()) {رمي جديد interruptedException ("العملية المقاطعة") ؛ } // ضع المستقبل في تجمع الطريق في انتظار pool.queue (المستقبل) ؛ // ضع المستقبل في تجمع الاتصال الكبير في انتظار This.pending.add (المستقبل) ؛ // إذا انتظرت إخطار الإشارة ، فإن النجاح صحيح إذا (الموعد النهائي! = null) {success = this.condition.awaituntil (الموعد النهائي) ؛ } آخر {this.condition.await () ؛ النجاح = صحيح ؛ } if (future.iscancelled ()) {رمي جديد interruptedException ("العملية المقاطعة") ؛ }} أخيرًا {// إزالة pool.unqueue (المستقبل) ؛ this.pending.remove (المستقبل) ؛ } // إذا لم يتم انتظار إشعار الإشعار وتوقيت الوقت الحالي ، يتم الخروج من الحلقة إذا (! Success & & }} // في النهاية ، لم يتم استلام أي إشعار بالإشارة ولم يتم الحصول على أي اتصال متاح ، تم طرح استثناء. رمي TimeOtexception ("Timeout في انتظار الاتصال") ؛ } أخيرًا {// قم بإطلاق القفل على تجمع الاتصال الكبير this.lock.unlock () ؛ }}هناك عدة نقاط مهمة في منطق الرمز أعلاه:
حتى الآن ، حصل البرنامج على مثيل CPOOLENTRY متاح ، أو أنهى البرنامج من خلال إلقاء استثناء.
4.3 httpclientConnection
HTTPClientConnection LeaseConnection (المستقبل النهائي <cpoolentry> المستقبل ، القوات النهائية الطويلة ، نوبة الوقت النهائية) تايمونيت) interruptedException ، ExecutionException ، ConnectionPooltimeOutException {Final Cpoolentry Entry ؛ جرب {// الحصول على إدخال cpoolentry من العملية غير المتزامنة في المستقبل <cpoolentry> entry = future.get (timeout ، Tunit) ؛ if (entry == null || future.iscancelled ()) {throw new interruptedException () ؛ } Asserts.check (enter.getConnection ()! = null ، "إدخال البلياردو بدون اتصال") ؛ if (this.log.isdebugenabled ()) {this.log.debug ("connection Spared:" + format (entral) + FormatStats (intply.getRoute ())) ؛ } // احصل على كائن وكيل من cpoolentry ، وتتم جميع العمليات باستخدام نفس httpclientconnection return cpoolproxy.newproxy (إدخال) ؛ } catch (Final TimeOutException ex) {رمي New ConnectionPooltimeOutException ("Timeout في انتظار الاتصال من البلياردو") ؛ }} 5. كيفية إعادة استخدام الاتصالات المستمرة في httpclient؟
في الفصل السابق ، رأينا أن httpclient تحصل على اتصالات من خلال برك التوصيل ، ويحصل عليها من التجمع عندما يكون من الضروري استخدام الاتصالات.
المقابلة للفصل الثالث:
في الفصل 4 ، رأينا كيف يتعامل httpclient مع مشاكل 1 و 3 ، فكيف نتعامل مع السؤال الثاني؟
وهذا هو ، كيف يحدد HTTPClient ما إذا كان ينبغي إغلاق الاتصال بعد الاستخدام ، أو ينبغي وضعه في تجمع لإعادة استخدام الآخرين؟ انظر إلى رمز MainClientExec
// إرسال استجابة اتصال http = requestExecutor.execute (الطلب ، managedConn ، السياق) ؛ // الدفاع عن ما إذا كان سيتم إعادة استخدام الاتصال الحالي بناءً على استراتيجية إعادة الاستخدام إذا (reusestrategy.keepalive (الاستجابة ، السياق)) {// الاتصال الذي يجب إعادة استخدامه ، والحصول على وقت التوصيل ، بناءً على المهلة في الاستجابة النهائية المدة الطويلة = keepalivestrategy.getkeepalivation (الاستجابة ، السياق) ؛ if (this.log.isdebugenabled ()) {Final String s ؛ // timeout هو عدد المللي ثانية ، إن لم يتم تعيينه ، فهذا هو -1 ، لا توجد مهلة إذا كانت (المدة> 0) {s = "for" + maturation + "" + timeUnit.milliseconds ؛ } آخر {s = "إلى أجل غير مسمى" ؛ } this.log.debug ("يمكن الاحتفاظ بالاتصال على قيد الحياة" + s) ؛ } // تعيين وقت المهلة. عندما ينتهي الطلب ، سيقرر مدير الاتصال ما إذا كان سيتم إغلاقه أو إعادته إلى التجمع بناءً على وقت المهلة connholder.setValidfor (المدة ، timeunit.milliseconds) ؛ // قم بالتسجيل في الاتصال كـ connholder.markReusable () ؛ } آخر {// اشترك في الاتصال باعتباره connholder.markNonreusable () }يمكن ملاحظة أنه بعد حدوث طلب باستخدام اتصال ، هناك سياسة إعادة اتصال الاتصال لتحديد ما إذا كان سيتم إعادة استخدام الاتصال. إذا تم إعادة استخدامه ، فسيتم تسليمه إلى HTTPClientConnectionManager بعد النهاية.
إذن ما هو منطق سياسة تعدد الإرسال؟
الطبقة العامة DefaultConnectionReusestrategy يمتد DefaultConnectionReusestrategy {مثيل Public Static Final DefaultConnectionReusestrategy = جديد defaultConnectionReusestrategy () ؛ Override public boolean keepalive (استجابة httpresponse النهائية ، السياق httpcontext النهائي) {// الحصول على طلب httprequest النهائي من السياق النهائي httprequest request = (httprequest) context.getAttribution (httpcorecontext.http_request) ؛ if (request! = null) {// احصل على رأس رأس الاتصال النهائي [] connheaders = request.getheaders (httpheaders.connection) ؛ if (connheaders.length! = 0) {final tokeniterator ti = new basictokeniterator (new basicheaderiterator (connheaders ، null)) ؛ بينما (ti.hasnext ()) {Final String Token = ti.nextToken () ؛ // إذا تم تضمين الاتصال: يتم تضمين رأس الإغلاق ، فهذا يعني أن الطلب لا يعتزم الحفاظ على الاتصال ، وسيتم تجاهل نية الاستجابة. هذا الرأس هو مواصفات http/1.1 if (http.conn_close.equalsignorecase (token)) {return false ؛ }}}}} // استخدم استراتيجية إعادة الاستخدام لفئة الأصل لإرجاع Super.keepalive (الاستجابة ، السياق) ؛ }}ألقِ نظرة على استراتيجية إعادة الاستخدام في فئة الوالدين
if (canResponseHaveBody (request ، response)) {Final header [] clhs = response.getheaders (http.content_len) ؛ // إذا لم يتم تعيين طول المحتوى للاستجابة بشكل صحيح ، فلن يتم إعادة استخدام الاتصال // لأنه بالنسبة للاتصالات المستمرة ، فلا داعي لإعادة تأسيس العلاقة بين الإرسالتين ، فأنت بحاجة إلى تأكيد طلب المحتوى الذي ينتمي إليه بشكل صحيح على أساس طول المحتوى (CLH) CLHS [0] ؛ حاول {Final int contentLen = Integer.Parseint (clh.getValue ()) ؛ if (contentLen <0) {return false ؛ }} catch (Final numberFormatexception ex) {return false ؛ }} آخر {return false ؛ }} if (headerIriterator.hasNext ()) {try {final tokeniterator ti = new basictokeniterator (headeriTerator) ؛ Boolean Keepalive = false ؛ بينما (ti.hasnext ()) {Final String Token = ti.nextToken () ؛ // إذا كان للاستجابة اتصال: إغلاق رأس ، يتم ذكر صراحة أنه يجب إغلاقه ، وإذا كان (http.conn_close.equalsignorecase (رمز)) {return false ؛ // إذا كانت الاستجابة لها اتصال: رأس الحفاظ ، يتم ذكر صراحة أنه يجب استمراره ، يتم إعادة استخدامه} آخر إذا (http.conn_keep_alive.equalsignorecase (رمز)) {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 ، عند إعادة استخدام الاتصال من تجمع الاتصال ، سيتحقق مما إذا كان ينتهي صلاحيته ، وتنظيفه إذا انتهت صلاحيته.
النسخة اللاحقة ستكون مختلفة. سيكون هناك مؤشر ترابط منفصل لمسح الاتصالات في تجمع الاتصال. بعد العثور على وجود استخدام أخير للوقت الذي تم تعيينه ، سيتم تنظيفه. المهلة الافتراضية هي ثانيتين.
Public ClovableHttpClient Build () {// إذا حددت أنك تريد تنظيف الاتصالات منتهية الصلاحية والخمول ، فسيتم بدء تشغيل مؤشر ترابط التنظيف. لا يتم بدء الافتراضي إذا (evictexpiredConnections || evictidleConnections) {// إنشاء مؤشر ترابط نظيف لمجموعة اتصال نهائية idleconnectionevictor connectionevictor = new idleconnectionevictor (cm ، maxidletime> 0؟ maxidletime: 10 ، maxidletimeunit! = null؟ maxidletimeunit) ؛ closeBlescopy.add (new closleable () {Override public void close () يلقي ioException {connectionevictor.shutdown () ؛ try {connectionEvictor.AwaitTermint (1L ، timeUnit.seconds) ؛} catch (InterruptedException interrupt) {thread.currentthread () ؛ // تنفيذ Cleaning Thread ConnectionEvictor.start () ؛}يمكنك أن ترى أنه عند بناء httpclientbuilder ، إذا تم تحديد وظيفة التنظيف ، فسيتم إنشاء مؤشر ترابط تنظيف تجمعات الاتصال وتشغيله.
Public IdleConnectionevictor (HTTPClientConnectionManager ConnectionManager ، ThreadFactory Final ، النهائي الطويل للنوم ، TimeUnit Final TimeUnit ، Final Long MaxIdletime ، Final TimeUnit MaxIdletimeUnit) this.threadfactory = threadfactory! = null؟ ThreadFactory: DefaultThReadFactory () جديد ؛ this.sleeptimems = sleeptimeunit! = null؟ SleeptimeUnit.tomillis (النوم): النوم ؛ this.maxidletimems = maxidletimeunit! = null؟ maxidletimeunit.tomillis (maxidletime): maxidletime ؛ this.thread = this.threadfactory.newthread (new RunNable () {Override public void run () {try {// the dead loop ، يستمر مؤشر الترابط في التنفيذ أثناء (! thread.currentThread (). ConnectionManager.closeexpiredConnections () ؛ }لتلخيص:
7. ملخص هذا المقال
يعتمد البحث أعلاه على الفهم الشخصي لرمز مصدر HTTPClient. إذا كان هناك أي خطأ ، آمل أن يترك الجميع رسالة لمناقشتها بنشاط.
حسنًا ، ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.