1. Antecedentes
O protocolo HTTP é um protocolo sem estado, ou seja, cada solicitação é independente um do outro. Portanto, sua implementação inicial é que toda solicitação HTTP abrirá uma conexão de soquete TCP e a conexão será fechada após a conclusão da interação.
O protocolo HTTP é um protocolo duplex completo, por isso são necessários três apertos de mão e quatro ondas para estabelecer e desconectar. Obviamente, neste design, toda vez que envio uma solicitação HTTP, ele consome muitos recursos extras, a saber, o estabelecimento e a destruição da conexão.
Portanto, o protocolo HTTP também foi desenvolvido e a multiplexação por conexão de soquete é realizada através de métodos de conexão persistentes.
Da foto, você pode ver:
Existem duas implementações de conexões persistentes: conexões de Keep-Alive e persistentes de HTTP/1.1 para HTTP/1.0+.
2. Keep-alive para http/1.0+
Desde 1996, muitos navegadores e servidores HTTP/1.0 estenderam o protocolo, ou seja, o protocolo de extensão "Keep-Alive".
Observe que este protocolo de extensão aparece como um complemento para 1,0 "conexão persistente experimental". O Keep-Alive não é mais usado e não é explicado na especificação mais recente do HTTP/1.1, mas muitos aplicativos continuaram.
Os clientes que usam HTTP/1.0 Adicionar "Conexão: Keep-Alive" ao cabeçalho e solicitem ao servidor que mantenha uma conexão aberta. Se o servidor estiver disposto a manter essa conexão aberta, ele incluirá o mesmo cabeçalho na resposta. Se a resposta não contiver o cabeçalho "Conexão: Keep-Aliving", o cliente pensará que o servidor não suporta o Keep-Alive e fechará a conexão atual após o envio da mensagem de resposta.
Através do protocolo suplementar de Keep-Alive, uma conexão persistente é concluída entre o cliente e o servidor, mas ainda existem alguns problemas:
3. Conexão persistente de http/1.1
O HTTP/1.1 leva um método de conexão persistente para substituir o Keep-Alive.
As conexões HTTP/1.1 são persistentes por padrão. Se você deseja fechar explicitamente, precisará adicionar a conexão: feche o cabeçalho à mensagem. Ou seja, no HTTP/1.1, todas as conexões são multiplexadas.
No entanto, como o Keep-Alive, as conexões persistentes ociosas podem ser fechadas pelo cliente e pelo servidor a qualquer momento. Não enviar a conexão: Fechar não significa que o servidor promete que a conexão permanecerá aberta para sempre.
4. Como gerar conexões persistentes por httpclient
HttpClien usa um pool de conexão para gerenciar as conexões de retenção. No mesmo link TCP, as conexões podem ser reutilizadas. O HTTPClient conecta a persistência através do pool de conexões.
De fato, a tecnologia "pool" é um design geral, e sua ideia de design não é complicada:
Todos os pools de conexão têm essa ideia, mas quando olhamos para o código -fonte HTTPClient, focamos principalmente em dois pontos:
4.1 Implementação do pool de conexão httpclient
O processamento de conexões persistentes pelo HTTPClient pode ser refletido no código a seguir. O seguinte é extrair as peças relacionadas ao pool de conexões do MainClientExec e remover outras peças:
public class MainClientExec implementa o clientExecchain {@Override public CloseableHttpResponse Execute (rota final httproute, solicitação final httprequestwrapper, httpclientCiExt Final, httPexiNCENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENTENCIONSTROTTHON {HTTPexecutionAWare Execare) lança o septor de reextt, htTPexceXceMception: ConnectionRequest final ConnectionRequest ConnRequest = ConnManager.RequestConnection (rota, Usertoken); Final HttpClientConnection ManageDConn; final int timeout = config.getConnectionRequestTimeout (); // Obtenha uma conexão gerenciada da conexão Conexão ConnectionRequestHttpClientConnection ManageDConn = Connrequest.get (Timeout> 0? Timeout: 0, timeUnit.millisEconds); // Envie o gerenciador de conexões httpclientConnectionManager e a conexão gerenciada httpclientConnection para um detentor de conexão com a conexão final do conexão conexão conexão = new ConnectionHolder (this.log, this.connmanager, gerenciado); tente {resposta httpResponse; if (! gerenciadoConn.isopen ()) {// Se a conexão atualmente gerenciada não estiver em um estado aberto, você precisará restabelecer a conexão estabelecida (proxyauthstate, gerenciado, rota, solicitação, contexto); } // Envie a solicitação através da conexão HTTPCLIENTCONNECTION Response = requestExecutor.execute (request, gerenciado, context); // distingue se a conexão pode ser reutilizada através da estratégia de reutilização de conexão se (reusestrategy.keepalive (resposta, contexto)) {// obtenha o período de validade da conexão duração final = keepalivestrategy.getkeepalivedration (resposta, contexto); // Defina o período de validade da conexão Connholder.SetValidFor (duração, timeUnit.millisEconds); // marque a conexão atual como Estado reutilizável Concholder.MarkReUsable (); } else {ConnHolder.MarkNonReUsable (); }} Final HttPentity entity = Response.getEntity (); if (entity == null ||! entity.isStreaming ()) {// Libere a conexão atual com o pool para a próxima chamada para conholder.releaseconnection (); devolver novo httpropseproxy (resposta, nulo); } else {return httproponseProxy (resposta, conexão); }}Aqui vemos que o processamento de conexões durante o processo de solicitação HTTP é consistente com as especificações do protocolo. Aqui discutiremos a implementação específica.
PoolingHttpClientConnectionManager é o gerenciador de conexão padrão do HTTPClient. Primeiro, obtenha uma solicitação de conexão através do requestConnection (). Observe que isso não é uma conexão.
Public ConnectionRequest RequestConnection (rota final httproute, estado final do objeto) {Final Future <CPoolEntry> futuro = this.pool.lease (rota, estado, nulo); retornar new ConnectionRequest () {@Override public boolean cancel () {return Future.cancel (true); } @Override Public HttpClientConnection Get (tempo limite final de longa data, timeunit final) lança interruptedException, ExecutionException, ConnectionPoolTimeOutException {final httpclientConnection Conn = LeaseConnection (Future, Timeout, Tunit); if (conn.isopen ()) {host final httphot; if (route.getProxyHost ()! = null) {host = route.getProxyHost (); } else {host = route.gettargetHost (); } Final SocketConfig SocketConfig = ResolveSocketConfig (host); Conn.SetSockettimeout (SocketConfig.getSotimeout ()); } retornar Conn; }}; }Você pode ver que o objeto ConnectionRequest retornado é na verdade uma instância de conexão real que contém o futuro <cpoolentry>, que é gerenciado pelo pool de conexões.
Do código acima, devemos nos concentrar:
Future<CPoolEntry> future = this.pool.lease(route, state, null)
Como obter uma conexão assíncrona de um pool de conexão cpool, futuro <cpoolentry>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
Como obter uma conexão verdadeira por conexão assíncrona com o futuro <cpoolentry>
4.2 FUTURO <CPOOLENTRY>
Vamos dar uma olhada em como o CPOOL lança um futuro <Cpoolentry>. O código central do abstrateConnpool é o seguinte:
Private E GetPoolEntRyBlock (rota final, estado de objeto final, tempo de tempo final final, timeunit final, futuro <e> futuro) lança ioexception, interruptedException, timeoutException {// primeiro bloqueia o pool de conexão atual. O bloqueio atual é um reentrantlockthis.lock.lock (); tente {// Obtenha um pool de conexão correspondente ao HTTPROUTE atual. Para o pool de conexões do httpclient, o pool total tem um tamanho e a conexão correspondente a cada rota também é um pool, por isso é um "pool no pool" final rotas de específico RoutesCifol <t, c, e> pool = getpool (rota); E entrada; para (;;) {asserts.check (! this.isshutdown, "Conection pool desligado"); // Obtenha conexões do pool correspondente à rota, que pode ser nula ou uma entrada de conexão válida = pool.getfree (estado); // Se você ficar nulo, saia do loop if (entradas == null) {break; } // Se você obtiver uma conexão expirada ou a conexão foi fechada, libere o recurso e continue a fazer loop para obter se (Entry.isexired (System.currenttimemillis ())) {Entry.close (); } if (entradas.isclosed ()) {this.available.remove (entrada); Pool.Free (entrada, false); } else {// Se você obtiver uma conexão válida, saia da quebra do loop; }} // Se você obtiver uma conexão válida, saia if (entradas! = Null) {this.Available.Remove (entrada); this.Leased.add (entrada); onreuse (entrada); entrada de retorno; } // Para provar aqui que nenhuma conexão válida foi obtida, você precisa gerar um int maxperRoute final = getMax (rota); // O número máximo de conexões correspondentes a cada rota é configurável. Se exceder, você precisará limpar algumas conexões através do LRU final int Excess = Math.max (0, pool.getAllocatedCount () + 1 - maxperRoute); if (excesso> 0) {for (int i = 0; i <excesso; i ++) {final e lastUsuD = pool.getLastUSED (); if (lastUsUt == null) {break; } lastUsUs.Close (); this.Available.Remove (LastUsoud); Pool.Remove (LastUsed); }} // O número de conexões no pool de rotas atual não atingiu o online se (pool.getAllocatedCount () <maxperroute) {final int totalUSED = this.LEASED.SIZE (); final int freeCapacity = math.max (this.maxtotal - totalUSEU, 0); // julga se o pool de conexões excede a linha on -line. Se exceder, você precisará limpar algumas conexões através da LRU se (freeCapacity> 0) {final int totalAVALABLE = this.Available.size (); // Se o número de conexões gratuitas já for maior que o espaço restante disponível, você precisará limpar a conexão gratuita se (total elogia> freeCapacity - 1) {if (! This.Available.ISEMPTY ()) {final e LastUsuD = this.Avilable.RemoVelast (); lastUsUs.Close (); Final RoutEspecificPool <t, c, e> Otherpool = getpool (lastUsuD.GetRoute ()); outrosPool.Remove (LastUsoud); }} // Crie uma conexão com base na rota final c Conn = this.connfactory.create (rota); // coloque essa conexão no "pequeno pool" correspondente para a entrada da rota = pool.add (conn); // Coloque essa conexão no "grande pool" this.laved.add (entrada); entrada de retorno; }} // Para esse fim, está provado que não há uma conexão válida do pool de rotas obtidas e, quando você deseja estabelecer uma conexão, o pool de conexão de rota atual atingiu seu valor máximo, ou seja, já existe uma conexão em uso, mas não está disponível para o thread atual, sucesso boolean = false; tente {if (future.isccelled ()) {throw new interruptedException ("Operação interrompida"); } // Coloque o futuro no pool de rota esperando Pool.queue (futuro); // coloque o futuro no grande pool de conexões esperando por isso.pending.add (futuro); // Se você aguarda a notificação do semáforo, o sucesso é verdadeiro se (prazo! } else {this.condition.await (); sucesso = true; } if (FUTURE.ISCANCELED ()) {THLHE NOVA INTERRUPEDEXCECCECTION ("Operação interrompida"); }} finalmente {// remove pool.unqueue (futuro); this.pending.remove (futuro); } // Se a notificação de semáforo não for esperada e o horário atual foi exagerado, o loop é excitado se (! Sucesso && (prazo! = Null && deve }} // No final, nenhuma notificação de semáforo foi recebida e nenhuma conexão disponível foi obtida, uma exceção foi lançada. lançar uma nova timeoutException ("Timeout Waiting for Connection"); } finalmente {// libere o bloqueio no grande pool de conexões this.lock.unlock (); }}Existem vários pontos importantes na lógica do código acima:
Até agora, o programa obteve uma instância de cpoolentry disponível ou encerrou o programa lançando uma exceção.
4.3 HttpClientConnection
LEASECONNEÇÃO DE LEASCONENCIONAÇÃO HTTPCLIENTCENCIONAL DE HTTPCLIENCIONAIS (FUTURO FINAL <CPoolEntry> Future, Timeout final final, Tunit final da unidade de tempo) lança interrupções de exceção, executionException, ConnectionPoolTimeOutException {entrada final cpoolentry; tente {// Obtenha a entrada cpoolentry da operação assíncrona futura <cpoolentry> entrada = futuro.get (tempo limite, tunit); if (entradas == null || future.cenceled ()) {tiro new interruptEdException (); } Asserts.check (Entry.getConnection ()! = NULL, "Entrada do pool sem conexão"); if (this.log.isdebugenabled ()) {this.log.debug ("conexão poupada:" + formato (entrada) + formatStats (entrada.getroute ())); } // Obtenha um objeto proxy do CPOOLENTRY, e as operações são feitas usando o mesmo httpclientConnection Return cpoolProxy.newproxy subjacente (entrada); } catch (timeoutException final ex) {lança new ConnectionPoolTimeoutException ("Tempo limite aguardando a conexão do pool"); }} 5. Como reutilizar conexões persistentes no httpclient?
No capítulo anterior, vimos que o HTTPClient obtém conexões através de pools de conexão e os pega da piscina quando for necessário usar conexões.
Correspondente ao terceiro capítulo:
No capítulo 4, vimos como o HTTPClient lida com os problemas de 1 e 3, então como lidamos com a segunda pergunta?
Ou seja, como o httpclient determina se uma conexão deve ser fechada após o uso ou deve ser colocada em uma piscina para que outros reutilizem? Veja o código do mainClientExec
// Envie a resposta de conexão HTTP = requestExecutor.execute (solicitação, gerenciado, contexto); // Defenda se a conexão atual deve ser reutilizada com base na estratégia de reutilização se (reusestrategy.keepalive (resposta, contexto)) {// a conexão que precisa ser reutilizada, obtenha o tempo limite da conexão, com base no tempo limite na resposta final de longa duração = keepAlivategsrate.getkeepaliveduration (resposta, contexto); if (this.log.isdebugenabled ()) {final string s; // Tempo limite é o número de milissegundos, se não estiver definido, é -1, ou seja, não há tempo limite se (duração> 0) {s = "para" + duração + "" + timeUnit.millisEconds; } else {s = "indefinidamente"; } this.log.debug ("A conexão pode ser mantida viva" + s); } // Defina o tempo de tempo limite. Quando a solicitação terminar, o Gerenciador de conexões decidirá se fechará ou o colocará de volta no pool com base no tempo limite do tempo limite, Conflitora.SetValidFor (duração, timeUnit.millisEconds); // Inscreva -se a conexão como reutilizável coatholder.markreusable (); } else {// Inscreva a conexão como conexão não reutilizável.marknonReusable (); }Pode -se observar que, depois que uma solicitação ocorre usando uma conexão, existe uma política de repetição de conexão para decidir se a conexão deve ser reutilizada. Se for reutilizado, será entregue ao HttpClientConnectionManager após o final.
Então, qual é a lógica da política de multiplexação de conexão?
classe pública DefaultClientConnectionReUSEstrategy estende o DefaultConnectionReUSESTRATÉGIA {public static final DefaultClientConnectionReUSEstrategy Instância = new DefaultClientConnectionReSestrategy (); @Override public boolean Keepalive (resposta final httpro -resposta, contexto final httpContext) {// Obtenha solicitação final httprequest do contexto final httprequest request = (httprequest) context.getAttribute (httpcorecontext.htPrequest); if (request! = null) {// Obtenha o cabeçalho do cabeçalho [] ConnHeaders = request.getheaders (httpheaders.connection); if (ConnHeaders.Length! = 0) {Final Tokeniterator Ti = new BasicTokeniterator (New BasicheadeRiterator (ConnHeaders, NULL)); while (ti.hasnext ()) {final string token = ti.nextToken (); // Se a conexão: fechar o cabeçalho estiver incluído, significa que a solicitação não pretende manter a conexão e a intenção da resposta será ignorada. Este cabeçalho é a especificação de http/1.1 if (http.conn_close.equalsignorecase (token)) {return false; }}}}} // Use a estratégia de reutilização da classe pai para retornar super.keepalive (resposta, contexto); }}Dê uma olhada na estratégia de reutilização da classe pai
if (CANRESPOnsionHaveBody (solicitação, resposta)) {Cabeçalho final [] CLHS = Response.getheaders (http.content_len); // Se o comprimento de conteúdo da resposta não estiver definido corretamente, a conexão não será reutilizada // porque, para conexões persistentes, não há necessidade de restabelecer a conexão entre as duas transmissões, você precisa confirmar qual solicitação o conteúdo pertence a uma resposta com base no conteúdo para lidar com o conteúdo corretamente o que não é o que não é o pacote de fenômeno ", que é o comprimento do conteúdo do conteúdo, sem que o comprimento do conteúdo não se sinta. = CLHS [0]; tente {final int contentlen = Integer.parseint (clh.getValue ()); if (contentlen <0) {return false; }} Catch (número finalMorMatexception ex) {return false; }} else {return false; }} if (headeriterator.hasNext ()) {try {final tokeniterator ti = new BasicTokeNiterator (headeriterator); Boolean Keepalive = false; while (ti.hasnext ()) {final string token = ti.nextToken (); // Se a resposta tiver uma conexão: feche o cabeçalho, é explicitamente declarado que deve ser fechado e se (http.conn_close.equalsignorecase (token)) {return false; // Se a resposta tiver uma conexão: o cabeçalho Keep-Alive, é explicitamente afirmado que deve ser persistido, é reutilizado} senão se (http.conn_keep_alive.equalsignorecase (token)) {ketalive = true; }} if (Keepalive) {return true; }} catch (parseException final px) {return false; }}} // Se não houver descrição do cabeçalho da conexão relevante na resposta, todas as conexões mais altas que as versões HTTP/1.0 serão reutilizadas para retornar! Ver.lessEquals (httpversion.http_1_0);Para resumir:
Como pode ser visto no código, sua estratégia de implementação é consistente com as restrições das camadas do protocolo Capítulo 2 e Capítulo 3.
6. Como limpar as conexões expiradas do httpclient
Antes de httpclient 4.4, ao reutilizar a conexão do pool de conexões, ele verificará se expira e limpá -lo se expirar.
A versão subsequente será diferente. Haverá um encadeamento separado para digitalizar as conexões no pool de conexões. Depois de descobrir que há um último uso do tempo que foi definido, ele será limpo. O tempo limite padrão é de 2 segundos.
Public CloseableHttpClient Build () {// Se você especificar que deseja limpar as conexões expiradas e ociosas, o thread de limpeza será iniciado. O padrão não é iniciado se (despetExPirdConnections || despetIdleConnections) {// Crie um fio limpo para um pool de conexões Final IdleConnectionEvictor ConnectionEvictor = new IdleConnectionEvictor (cm, maxidleTime> 0? MaxidleTime: 10IrtEnTetEnit.Mimes, maxidleTime> 0? MaxidleThetLeTleTetLetLeTetLetLeTETETETET.MIDETELETELETETELETETELETETETETETIMET. maxidletimeUnit); closabescopy.add (new Closeable () {@Override public void Close () lança IoException {ConnectionEvictor.shutdown (); Try {ConnectionEvictor.awaitMinageNin (1L, timeUnit.SeConds);} Catch (Final InterruptException) {Thread.CurntThs).; // Execute o encadeamento de limpeza ConnectionEvictor.start ();}Você pode ver que, quando httpclientbuilder estiver construindo, se a função de limpeza estiver ativada for especificada, um encadeamento de limpeza do pool de conexão será criado e executá -lo.
Public IdleConnectionEvictor (Final HttpClientConnectionManager ConnectionManager, Threads Final Flugtory, Final Longo SleepTime, TimeUnit Final SleepTimeUnit, Longo Máxidleto final, TimeUnit final MAXIDLETIMEUNIT) {this.ConnectionManager = Args.NotNull (ConnectionManager, "Gerenciador"); 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 () {try {// The Dead Loop, o Thread continua sendo executado while (! Thread.currentThread (). ConnectionManager.closeExiredConnection (); }Para resumir:
7. Resumo deste artigo
A pesquisa acima é baseada no entendimento pessoal do código -fonte httpclient. Se houver algum erro, espero que todos deixem uma mensagem para discutir isso ativamente.
Ok, o acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.