1. Фон
Протокол HTTP является протоколом без сохранения состояния, то есть каждый запрос не зависит друг от друга. Следовательно, его первоначальная реализация заключается в том, что каждый HTTP -запрос откроет подключение к сокетам TCP, и соединение будет закрыто после завершения взаимодействия.
Протокол HTTP является полным дуплексным протоколом, поэтому для установления и отключения требуется три рукопожатия и четыре волны. Очевидно, что в этом дизайне каждый раз, когда я отправляю HTTP -запрос, он потребляет много дополнительных ресурсов, а именно установление и уничтожение связи.
Следовательно, также был разработан протокол HTTP, а мультиплексирование соединения сокета выполняется с помощью постоянных методов соединения.
На картинке вы можете увидеть:
Существует две реализации постоянных соединений: Keep-Alive и постоянные соединения HTTP/1.1 для HTTP/1.0+.
2. Keep-Alive для HTTP/1,0+
С 1996 года многие браузеры и серверы HTTP/1.0 расширили протокол, то есть протокол расширения «Keep-Alive».
Обратите внимание, что этот протокол расширения появляется как дополнение к 1,0 «Экспериментальное постоянное соединение». Keep-Alive больше не используется, и это не объясняется в последней спецификации HTTP/1.1, но многие приложения продолжались.
Клиенты, использующие http/1.0, добавляют «соединение: gope-alive» к заголовку, и запросите сервер, чтобы подключить соединение открытым. Если сервер готов оставить это соединение открытым, он будет включать в себя тот же заголовок в ответе. Если ответ не содержит заголовка «Подключение: gore-alive», клиент будет думать, что сервер не поддерживает Keep-alive и закроет текущее соединение после отправки ответного сообщения.
С помощью дополнительного протокола Keep-Alive заполняется постоянное соединение между клиентом и сервером, но все еще есть некоторые проблемы:
3. Постоянное соединение HTTP/1.1
HTTP/1.1 принимает метод постоянного соединения для замены Keep-Alive.
Подключения http/1.1 по умолчанию стойкие. Если вы хотите явно закрыться, вам нужно добавить подключение: Закрыть заголовок к сообщению. То есть в http/1.1 все соединения мультиплексированы.
Тем не менее, как и в целом, постоянные подключения на холостом ходу могут быть закрыты клиентом и сервером в любое время. Не отправлять подключение: Закрытие не означает, что сервер обещает, что соединение останется открытым навсегда.
4. Как создать постоянные соединения с помощью httpclient
Httpclien использует пул соединений для управления подключениями к удержанию. На той же ссылке TCP подключения могут быть использованы повторно. Httpclient соединяет постоянство посредством объединения соединений.
На самом деле, технология «пула» является общим дизайном, и ее идея дизайна не сложна:
У всех пулов соединений есть эта идея, но когда мы смотрим на исходный код HTTPClient, мы в основном сосредоточены на двух точках:
4.1 Реализация пула соединений HTTPClient
Обработка постоянных соединений HttpClient может быть отражена в следующем коде. Следующее извлечение деталей, связанных с пулом соединений из MainClientExec, и удалить другие детали:
открытый класс MainClientExec реализует ClientExecCchain {@Override public keepableHttpresponse execute (окончательный httproute route route, окончательный запрос httprequestwrapper, окончательный контекст httpclientcontext, окончательный httpexecutionaware execkarware), httpexception {// get a aepressemancemangecrecrecrage httpexception { /get a aepressecrectioncongecrecrage httpexcept ConnectionRequest final ConnectionRequest connrequest = connmanager.requestConnection (route, usertoken); окончательный httpclientconnection managedconn; final int timeout = config.getConnectionRequestTimeout (); // Получить управляемое соединение из запроса подключения ConnectionRequesthttpClientConnection ManagedConn = connrequest.get (Timeout> 0? Timeout: 0, TimeUnit.milliseconds); // отправить менеджер подключения httpclientConnectionManager и управляемое соединение httpclientConnection с подключением к подключению держит окончательный держатель соединения Connholder = new Connectionholder (this.log, this.connmanager, ManagedConn); try {httpresponse response; if (! ManagedConn.isopen ()) {// Если в настоящее время управляемое соединение не находится в открытом состоянии, вам необходимо восстановить установленное соединение (ProxyAuthstate, ManagedConn, маршрут, запрос, контекст); } // Отправить запрос через подключение httpclientConnection response = requestExecutor.execute (request, managedconn, context); // различать, можно ли использовать связь повторно с помощью стратегии повторного использования соединения, если (reuseStrategy.keepalive (response, context)) {// Получить период достоверности соединения окончательный длительный срок = keepalivestrategy.getkeepaleAlvation (ответ, контекст); // Установите период достоверности соединения Connholder.SetValidFor (продолжительность, TimeUnit.milliseconds); // отмечать текущее соединение как повторно используемого состояния connholder.markreusable (); } else {connholder.marknonReasable (); }} окончательный httpentity entity = response.getentity (); if (Entity == null ||! Entity.IsStreaming ()) {// Выпустите текущее соединение в пул для следующего вызова CONNOLDER.RELEASECONNECTE (); вернуть новый httpresponspoxy (response, null); } else {return new httpresponseproxy (response, connholder); }}Здесь мы видим, что обработка соединений в процессе HTTP -запроса согласуется со спецификациями протокола. Здесь мы обсудим конкретную реализацию.
PoolingHttpClientConnectionManager является менеджером подключения по умолчанию HTTPClient. Во -первых, получить запрос на соединение через requestConnection (). Обратите внимание, что это не соединение.
public connectionRequest requestConnection (окончательный httproute route, конечное состояние объекта) {final Future <cpoolEntry> future = this.pool.Lease (маршрут, состояние, null); return new ConnectionRequest () {@Override public boolean cancel () {return future.cancel (true); } @Override public httpclientConnection get (окончательный длительный тайм -аут, окончательный TimeUnit tunit) Throws ErruptEdException, executionException, ConnectionPooltimeOutexception {final httpClientConnection conn = leaseConnection (будущее, тайм -аут, 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 на самом деле является реальным экземпляром подключения, который содержит будущее <cpoolentry>, который управляется пулом соединений.
Из приведенного выше кода мы должны сосредоточиться:
Future<CPoolEntry> future = this.pool.lease(route, state, null)
Как получить асинхронное соединение из пула соединений Cpool, Future <poolentry>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
Как получить истинную связь с помощью асинхронной связи с будущим <poolentry>
4.2 Future <poolentry>
Давайте посмотрим, как Cpool выпускает будущее <cpoolentry>. Основной код AbstractConnpool заключается в следующем:
Private E GetPoolEntryBlocking (Final T маршрут, конечное состояние объекта, окончательный длительный тайм -аут, окончательный TimeUnit Tunit, Final Future <e> Future) бросает ioException, прерывание, прерывание, timeoutexception {// Сначала блокировать текущий пул соединений. Текущая блокировка - это повторное урегулирование. Lock.lock (); Попробуйте {// Получить пул соединений, соответствующий текущему httproute. Для пула соединений httpclient общий бассейн имеет размер, а соединение, соответствующее каждому маршруту, также является пулом, так что это «пул в пуле», окончательный маршрут, com, c, e> pool = getpool (маршрут); E вход; for (;;) {asserts.check (! this.isshutdown, "Pool Shut Down"); // Получить подключения из пула, соответствующего маршруту, который может быть нулевым, или допустимой записи соединения = pool.getFree (состояние); // Если вы получите NULL, выйдите из цикла if (intpirt == null) {break; } // Если вы получите срок действия соединения или соединение было закрыто, отпустите ресурс и продолжайте цикл, чтобы получить if (intry.isexpired (system.currenttimemillis ())) {entry.close (); } if (entry.isclosed ()) {this.available.remove (entry); pool.free (entry, false); } else {// Если вы получите действительное соединение, выйдите из перерыва в цикле; }} // Если вы получите действительное соединение, выйдите из if (intry! = Null) {this.available.remove (entry); это. OnReuse (запись); возвратный вход; } // здесь докажите, что не было получено достоверного соединения, вам необходимо генерировать окончательный int maxperroute = getmax (route); // Максимальное количество соединений, соответствующих каждому маршруту, настраивается. Если он превышает, вам необходимо очистить некоторые соединения через LRU Final Int Excess = Math.max (0, pool.getAllocatedCount () + 1 - maxperroute); if (Excess> 0) {for (int i = 0; i <Excess; i ++) {final e Lastude = pool.getLastused (); if (Lastustic == null) {break; } lastustic.close (); это. Pool.remove (Lasting Under); }} // Количество соединений в текущем пуле маршрутов не достигло онлайн if (pool.getallocatedCount () <maxperroute) {final int total Undal = this.Led.Size (); final int freecapacity = math.max (this.maxtotal - общее использование, 0); // Судите, превышает ли пул соединений онлайн -линию. Если он превышает его, вам нужно очистить некоторые соединения через LRU if (freecapacity> 0) {final int totalavailable = this.available.size (); // Если количество свободных соединений уже больше, чем оставшееся доступное пространство, вам необходимо очистить бесплатное соединение, если (totalAvailable> FreeCapacity - 1) {if (! This.available.isempty ()) {final e Lastudaud = this.available.removelast (); Lastusted.close (); final RoutePecificpool <T, C, E> onePool = getPool (Lastuly.getRoute ()); otherpool.remove (Lastusted); }} // Создать соединение на основе маршрута окончательно c conn = this.connfactory.create (route); // Поместите это соединение в «маленький пул», соответствующий route intry = pool.add (conn); // Поместите это соединение в «Большой пул» this.Led.add.Add (inpit); возвратный вход; }} // С этой целью доказано, что не существует достоверного соединения из полученного пула маршрутов, и когда вы хотите установить соединение самостоятельно, текущий пул подключения маршрута достиг своего максимального значения, то есть уже используется соединение, но оно недоступно для текущего логического успеха потока = false; try {if (future.iscancelled ()) {бросить новое прерывание ("Операция прервана"); } // Поместите будущее в пул маршрутов в ожидании Pool.Queue (Future); // Поместите будущее в большой пул соединений в ожидании этого. Ppending.Add (будущее); // Если вы ждете уведомления о семафоре, успех верно, если (крайний срок! } else {this.condition.await (); Успех = правда; } if (future.iscancelled ()) {throw new refruptedException ("Операция прервана"); }} наконец {// Удалить pool.unqueue (Future); this.pending.remove (будущее); } // Если уведомление о семафоре не будет ждается, и текущее время истекло, цикл выходит, если (! Успех && (Deadline! = Null && Deadline.getTime () <= System.currentTimeMillis ())) {break; }} // В конце концов, уведомление о семафоре не было получено, и не было получено никакого доступного соединения, было брошено исключение. бросить New TimeOutException («Тайм -аут ожидания подключения»); } Наконец {// Отпустите блокировку в большом пуле подключения this.lock.unlock (); }}В приведенной выше логике кода есть несколько важных моментов:
До настоящего времени программа получила доступный экземпляр CpoolEntry или прекратила программу, бросив исключение.
4.3 httpclientconnection
Защищенное httpclientConnection LeaseConnection (Final Future <cpoolEntry> Future, Final Long Timeout, Final TimeUnit TUNIT) бросает прерывания, выполняет, выполнение ExecutionException, ConnectionPooltimeOutexception {Окончательная запись CpoolEntry; try {// Получить вход CpoolEntry от асинхронной операции Future <poolEntry> entry = future.get (timeout, tunit); if (intry == null || future.iscancelled ()) {throw new refruptedException (); } Asserts.check (entry.getConnection ()! = Null, «запись в пуле без подключения»); if (this.log.isdebugenabled ()) {this.log.debug ("Соединение vorared:" + format (entry) + formatstats (entry.getroute ())); } // Получить прокси -объект CpoolEntry, и все операции выполняются с использованием одного и того же базового httpclientConnection return cpoolproxy.newproxy (entry); } catch (final TimeOutException ex) {Throw New ConnectionPooltimeOutexception («Тайм -аут ожидает соединения от пула»); }} 5. Как повторно использовать постоянные соединения в httpclient?
В предыдущей главе мы увидели, что httpclient получает соединения через пулы соединений и получает их из пула, когда необходимо использовать соединения.
Соответствует третьей главе:
В главе 4 мы увидели, как httpclient решает проблемы 1 и 3, так как же мы справляемся со вторым вопросом?
То есть, как httpclient определяет, следует ли закрыть соединение после использования, или оно должно быть помещено в пул для повторного использования? Посмотрите на код MainClientExec
// Отправить http connection response = requestexecutor.execute (request, managedconn, context); // защищать, должно ли текущее соединение использоваться повторно на основе стратегии повторного использования if (reuseStrategy.keepalive (ответ, контекст)) {// Связь, которое необходимо использовать повторно, получите время ожидания подключения, основываясь на времени ожидания в ответе окончательной длительной длительности = KeepAliveStrategy.GetkeePakeAkeAdeAdiveDuration (ответ, контекст); if (this.log.isdebugenabled ()) {final String s; // Тайм -аут -это количество миллисекундов, если не установлено, это -1, то есть нет тайм -аута, если (длительность> 0) {s = "для" + duration + "" + timeUnit.milliseconds; } else {s = "бесконечно"; } this.log.debug ("соединение может быть в живых" + s); } // Установите время ожидания. Когда запрос заканчивается, менеджер подключения решит, закрыть ли или поместить его обратно в пул в зависимости от времени, времени ожидания. // подписать соединение как повторно используемый Connholder.markReusable (); } else {// Зарегистрируйте соединение как не повторный connohlder.marknonReasable (); }Можно видеть, что после того, как запрос возникает с использованием подключения, существует политика повторной политики подключения, чтобы решить, следует ли использовать это соединение. Если он будет использован повторно, он будет передан HttpClientConnectionManager после конца.
Так в чем же логика политики мультиплексирования соединения?
открытый класс DefaultClientConnectionReaseRategy Extends defaultConnectionReaseRategy {public Static Final CaultConconnectionReectionReestrategy ancess = new DefaultClientConceectionReestrategy (); @Override public boolean keepalive (окончательный ответ httpresponse, окончательный контекст httpcontext) {// Получить запрос окончательного httprequest из контекста окончательного httprequest request = (httprequest) context.getattribute (httpcorecontext.http_request); if (request! = null) {// Получите окончательный заголовок подключения [] conneaders = request.getheaders (httpheaders.connection); if (connheaders.length! = 0) {final Tokeniterator ti = new BasictOkeniterator (новый 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 (canresponshavebody (request, response)) {final Header [] clhs = response.getheaders (http.content_len); // Если содержимое длины ответа не установлено правильно, соединение не будет повторно использовано // Поскольку для постоянных соединений нет необходимости восстанавливать соединение между двумя передачами, вам необходимо подтвердить, какое запрос содержимого принадлежит на основе длины содержимого, чтобы правильно обрабатывать феномен «Сток»/// Последний подключение. 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 BasictOkeniterator (HeaderIterator); логический keepalive = false; while (ti.hasnext ()) {final String token = ti.nexttoken (); // Если ответ имеет подключение: закрытие заголовка, явно заявляется, что он должен быть закрыт, и if (http.conn_close.equalsignorecase (token)) {return false; // Если ответ имеет подключение: заголовок keep-alive, явно заявляется, что он должен быть сохраняется, он повторно используется} else if (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 stockablehttpclient build () {// Если вы указываете, что вы хотите очистить истекшие и простоя подключения, будет запущена поток чистки. По умолчанию не начинается, если (evictexpiredConnections || evictidLeconnections) {// Создать чистую ветку для пула соединений Final Idleconnectionevictor connectionevictor = new idleconnectionevictor (cm, maxidletime> 0? maxidletimeunit); sloseablescopy.add (new closable () {@override public void cloid () бросает ioexception {connecteVictor.shutdown (); try {connectionevictor.awaittermination (1l, timeunit.seconds);} catch (окончательное прерывание. // Выполнить connectionevictor.start ();};};Вы можете видеть, что при создании HttpClientBuilder, если будет включена функция очистки, будет создана поток очистки пула соединений и запустить его.
public idleconnectionevictor (final httpclientConnectionManager ConnectionManager, Final Threadfactory Threadfactory, Final Long Sleeptime, Final TimeUnit Sleeptimit, Final Long Maxidletime, Final TimeUnit MaxidletimeUnit) {this.concectionManager = args.notnull (ConnectionManer, «Менеджер соединения»); this.ThreadFactory = ThinkFactory! = NULL? ThinkFactory: новый defaultThreadFactory (); this.sleeptimemes = 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 () {try {// мертвый цикл, поток продолжает выполнять, в то время как (! Thread.currentThread (). IseReverrated ()) {// выполняет после нескольких секунд, не дефолт, к 10 секундам. ConnectionManager.CloseExpiredConcections (); }Суммировать:
7. Резюме этой статьи
Приведенное выше исследование основано на личном понимании исходного кода HTTPClient. Если есть какие -либо ошибки, я надеюсь, что все оставят сообщение, чтобы активно обсудить его.
Хорошо, вышеупомянутое содержимое этой статьи. Я надеюсь, что содержание этой статьи имеет определенную справочную ценность для каждого обучения или работы. Если у вас есть какие -либо вопросы, вы можете оставить сообщение для общения. Спасибо за поддержку Wulin.com.