1. Contexte
Le protocole HTTP est un protocole sans état, c'est-à-dire que chaque demande est indépendante les unes des autres. Par conséquent, sa mise en œuvre initiale est que chaque demande HTTP ouvrira une connexion de socket TCP et que la connexion sera fermée une fois l'interaction terminée.
Le protocole HTTP est un protocole complet du duplex, il faut donc trois poignées de main et quatre vagues pour établir et déconnecter. De toute évidence, dans cette conception, chaque fois que j'envoie une demande HTTP, il consomme beaucoup de ressources supplémentaires, à savoir l'établissement et la destruction de la connexion.
Par conséquent, le protocole HTTP a également été développé et le multiplexage des connexions de socket est effectué par des méthodes de connexion persistantes.
De l'image, vous pouvez voir:
Il existe deux implémentations de connexions persistantes: les connexions de conservation et de persistance de HTTP / 1.1 pour HTTP / 1.0 +.
2. Gardez-vous pour HTTP / 1.0 +
Depuis 1996, de nombreux navigateurs et serveurs HTTP / 1.0 ont étendu le protocole, c'est-à-dire le protocole d'extension "Keep-Alive".
Notez que ce protocole d'extension apparaît comme un complément à 1,0 "connexion persistante expérimentale". La gardien de main n'est plus utilisée, et elle n'est pas expliquée dans la dernière spécification HTTP / 1.1, mais de nombreuses applications se sont poursuivies.
Les clients utilisant HTTP / 1.0 ajoutent "Connexion: Keep-Alive" à l'en-tête et demandez au serveur de garder une connexion ouverte. Si le serveur est prêt à garder cette connexion ouverte, elle inclura le même en-tête dans la réponse. Si la réponse ne contient pas l'en-tête "Connexion: Keep-Alive", le client pense que le serveur ne prend pas en charge Keep-Alive et fermera la connexion actuelle après avoir envoyé le message de réponse.
Grâce au protocole supplémentaire de Keep-Alive, une connexion persistante est terminée entre le client et le serveur, mais il y a encore quelques problèmes:
3. Connexion persistante de HTTP / 1.1
HTTP / 1.1 prend une méthode de connexion persistante pour remplacer la main-d'œuvre.
Les connexions HTTP / 1.1 sont persistantes par défaut. Si vous souhaitez fermer explicitement, vous devez ajouter la connexion: fermez l'en-tête au message. Autrement dit, dans HTTP / 1.1, toutes les connexions sont multiplexées.
Cependant, comme les connexions persistantes inactives et persistantes peuvent être fermées par le client et le serveur à tout moment. Ne pas envoyer de connexion: Close ne signifie pas que le serveur promet que la connexion restera ouverte pour toujours.
4. Comment générer des connexions persistantes par httpclient
Httpcllien utilise un pool de connexions pour gérer les connexions de maintien. Sur le même lien TCP, les connexions peuvent être réutilisées. HTTPClient relie la persistance grâce à la mise en commun de la connexion.
En fait, la technologie "Pool" est une conception générale, et son idée de conception n'est pas compliquée:
Tous les pools de connexion ont cette idée, mais lorsque nous regardons le code source HTTPClient, nous nous concentrons principalement sur deux points:
4.1 Implémentation du pool de connexion HTTPClient
Le traitement des connexions persistants par HttpClient peut être reflété dans le code suivant. Ce qui suit consiste à extraire les pièces liées au pool de connexion à partir de MainclientExec et à supprimer d'autres pièces:
classe publique MainClientExec implémente ClientExecChain {@Override public ClosableHttpResponse Execute (Final Httproute Route, final httprequestwrapper request, final httpclientContext context, final httpexecutionware execware) lance ioexception, httpexception {// obtenir une demande de connexion à partir du manager de connexion HTTPCONDECTCECT ConnectionRequest Final ConnectionRequest ConnRequest = ConnManager.RequestConnection (Route, Usertoken); final httpClientConnection ManagedConn; final int timeout = config.getConnectionRequestTimeout (); // obtient une connexion gérée à partir de la demande de connexion ConnectionRequesthTTPClientConnection ManagedConn = ConnRequest.get (Timeout> 0? Timeout: 0, timeUnit.Milliseconds); // Soumettez le Connection Manager httpClientConnectionManager et la connexion gérée HttpClientConnection à un Holder de connexion maintient le titulaire de connexion final ConnHolder = new ConnectionHolder (this.log, this.connManager, ManagedConn); Essayez {HttpResponse Response; if (! ManagedConn.isopen ()) {// Si la connexion actuellement gérée n'est pas à l'état ouvert, vous devez rétablir la connexion établie (ProxyAuthState, ManagedConn, route, demande, contexte); } // Envoi de la demande via la connexion httpClientConnection Response = requestExecutor.Execute (request, ManagedConn, context); // Distinguer si la connexion peut être réutilisée via la stratégie de réutilisation de la connexion if (reusestrategy.keepalive (réponse, contexte)) {// Obtenez la période de validité de connexion finale longue durée = keepalivestrategy.getKeepaliveduration (réponse, contexte); // Définit la période de validité de connexion ConnHolder.SetValidFor (durée, timeunit.milliseconds); // Marquez la connexion actuelle en tant qu'état réutilisable ConnHolder.markreusable (); } else {ConnHolder.MarkNonReusable (); }} final httpentity Entity = Response.getEntity (); if (entity == null ||! Entity.SiStreaming ()) {// Libérez la connexion actuelle au pool pour l'appel suivant à ConnHolder.ReleasEConnection (); Renvoie un nouveau httpResponseproxy (réponse, null); } else {return new httpResponseProxy (réponse, ConnHolder); }}Ici, nous constatons que le traitement de la connexion pendant le processus de demande HTTP est cohérent avec les spécifications du protocole. Ici, nous discuterons de l'implémentation spécifique.
PooringHttpClientConnectionManager est le gestionnaire de connexion par défaut de HttpClient. Premièrement, obtenez une demande de connexion via requestConnection (). Notez que ce n'est pas une connexion.
Public ConnectionRequest requestConnection (Final HttpRoute Route, Final Object State) {Final Future <CPoolEntry> futur = this.pool.lease (Route, State, Null); return new ConnectionRequest () {@Override public boolean annule () {return future.cancel (true); } @Override public httpClientConnection get (final long timeout, final timeunit tunit) lève l'interruption de l'interruption, EXECUTUTIONException, ConnectionPoolTimeoutException {final httpClientConnection Conn = leseConnection (future, timeout, tunit); if (conn.isopen ()) {Host httphost final; if (Route.getProxyHost ()! = null) {host = root.getProxyHost (); } else {host = Route.getTargeThost (); } socketconfig final socketconfig = résolveSocketConfig (hôte); Conn.SetSocketTimeout (socketconfig.getsoTimeout ()); } return conn; }}; }Vous pouvez voir que l'objet ConnectionRequest retourné est en fait une instance de connexion réelle qui contient futur <cpoolEntry>, qui est gérée par le pool de connexion.
D'après le code ci-dessus sur lequel nous devons nous concentrer:
Future<CPoolEntry> future = this.pool.lease(route, state, null)
Comment obtenir une connexion asynchrone à partir d'un pool de connexion cpool, futur <cpoolEntry>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
Comment obtenir une véritable connexion par une connexion asynchrone à Future <CPoolEntry>
4.2 Future <cpoolEntry>
Jetons un coup d'œil à la façon dont CPool publie un futur <cpoolEntry>. Le code central d'AbstractConnpool est le suivant:
Private E GetpoolEntryBlocking (Route T finale, État d'objet final, final de temps final, TimeUnit final TUNIT, Final Future <e> Future) lève IOException, InterruptedException, TimeEutException {// Verrouillez d'abord le pool de connexion actuel. Le verrouillage actuel est un reentrantlockthis.lock.lock (); Essayez {// Obtenez un pool de connexion correspondant au HTTPROUTE actuel. Pour le pool de connexion de HTTPClient, le pool total a une taille et la connexion correspondant à chaque itinéraire est également un pool, il s'agit donc d'un "pool dans la piscine" RoundspepecificPool <t, c, e> pool = getpool (itinéraire); E Entrée; pour (;;) {asserts.Check (! this.isshutdown, "Connection Pool Shut Down"); // Obtenez des connexions du pool correspondant à l'itinéraire, qui peut être nul, ou une entrée de connexion valide = pool.getFree (état); // Si vous obtenez NULL, quittez la boucle if (entrée == null) {Break; } // Si vous obtenez une connexion expirée ou si la connexion a été fermée, relâchez la ressource et continuez à boucler pour obtenir if (entrée.isexpired (System.CurrentTimemillis ())) {Entry.close (); } if (entry.isclosed ()) {this.available.reMove (entrée); pool.free (entrée, false); } else {// Si vous obtenez une connexion valide, quittez la pause boucle; }} // Si vous obtenez une connexion valide, sortez if (entrée! = Null) {this.available.remove (entrée); this.leed.add (entrée); onreuse (entrée); retour de retour; } // Pour prouver ici qu'aucune connexion valide n'a été obtenue, vous devez générer un int maxperRoute final = getMax (route); // Le nombre maximum de connexions correspondant à chaque itinéraire est configurable. S'il dépasse, vous devez nettoyer certaines connexions via LRU final int excès = math.max (0, pool.getAlloCateCount () + 1 - maxperRoute); if (excès> 0) {for (int i = 0; i <excès; i ++) {final e lastUsed = pool.getLastUsesed (); if (LastUsed == null) {break; } LastUsed.close (); this.vailable.remove (LastUsed); Pool.Remove (LastUsed); }} // Le nombre de connexions dans le pool d'itinéraire actuel n'a pas atteint le if en ligne (pool.getAllocatedCount () <maxperRoute) {final int totalUsed = this.leed.size (); final int freecapacity = math.max (this.maxtotal - TotalUsed, 0); // juger si le pool de connexions dépasse la ligne en ligne. S'il le dépasse, vous devez nettoyer certaines connexions via LRU if (freeCapacity> 0) {final int totalAvailable = this.vailable.size (); // Si le nombre de connexions libres est déjà supérieur à l'espace disponible restant, vous devez nettoyer la connexion gratuite si (totalAcavailable> freeCapacity - 1) {if (! This.available.isempty ()) {final e lastUsed = this.available.removelast (); LastUsed.close (); Final RouteSpecificPool <t, c, e> autrepool = getPool (LastUsed.getRoute ()); Otherpool.remove (LastUsed); }} // Créer une connexion basée sur la route finale c conn = this.connfactory.create (itinéraire); // Mettez cette connexion dans le "petit pool" correspondant à l'entrée de route = pool.add (Conn); // Mettez cette connexion dans le "grand pool" this.leed.add (entrée); retour de retour; }} // À cette fin, il est prouvé qu'il n'y a pas de connexion valide à partir du pool d'itinéraire obtenu, et lorsque vous souhaitez établir une connexion vous-même, le pool de connexion d'itinéraire actuel a atteint sa valeur maximale, c'est-à-dire qu'il y a déjà une connexion utilisée, mais elle n'est pas disponible pour le thread booléen Success = false; essayez {if (future.isCancelled ()) {Throw new interruptedException ("Operation Interrupted"); } // Mettez l'avenir dans la piscine d'itinéraire en attente de pool.queue (futur); // Mettez l'avenir dans le grand pool de connexion en attendant ceci.Pending.add (futur); // Si vous attendez la notification du sémaphore, le succès est vrai si (date limite! = Null) {Success = this.condition.Awaituntil (date limite); } else {this.condition.await (); succès = vrai; } if (future.isCancelled ()) {Throw New InterruptedException ("Operation Interrupted"); }} enfin {// supprimer pool.unqueue (futur); this.pending.remove (futur); } // Si la notification du sémaphore n'est pas attendue et que l'heure actuelle a chronométré, la boucle est sortie si (! Success && (Deadline! = Null && Deadline.getTime () <= System.Currenttimemillis ())) {break; }} // En fin de compte, aucune notification de sémaphore n'a été reçue et aucune connexion disponible n'a été obtenue, une exception n'a été lancée. Jetez un nouveau tempsoutException ("Timeout en attente de connexion"); } Enfin {// Libérez le verrou sur le grand pool de connexion this.lock.unlock (); }}Il y a plusieurs points importants dans la logique de code ci-dessus:
Jusqu'à présent, le programme a obtenu une instance CPOolEntry disponible ou a mis fin au programme en lançant une exception.
4.3 HttpClientConnection
HttpClientConnection HttpClientConnection protégé (Future Future <CPOolEntry> Future, final Timeout final, TimeUnit final Unit) lève InterruptedException, ExecutionException, ConnectionPoolTimeoutException {Entrée finale de CPOolEntry; Essayez {// Obtenez une entrée CPOolEntry de l'opération asynchrone future <cpoolEntry> entrée = futur.get (timeout, tunit); if (entry == null || futur.isCancelled ()) {lancer new interruptedException (); } Asserts.check (entrée.getConnection ()! = Null, "Entrée de pool sans connexion"); if (this.log.isdebugeNabled ()) {this.log.debug ("Connection Spared:" + format (entrée) + formatStats (entrée.getRoute ())); } // Obtenez un objet proxy de cpoolEntry, et les opérations sont toutes effectuées en utilisant le même sous-jacent httpclientconnection return cpoolProxy.newproxy (entrée); } catch (final timeoutException ex) {lancez new ConnectionPoolTimeOutException ("Timeout en attente de connexion à partir du pool"); }} 5. Comment réutiliser les connexions persistantes dans httpclient?
Dans le chapitre précédent, nous avons vu que HttpClient obtient des connexions via des pools de connexions et les obtient du pool lorsqu'il est nécessaire d'utiliser des connexions.
Correspondant au troisième chapitre:
Dans le chapitre 4, nous avons vu comment HttpClient gère les problèmes de 1 et 3, alors comment gérons-nous la deuxième question?
Autrement dit, comment HttpClient détermine-t-il si une connexion doit être fermée après une utilisation, ou devrait-elle être placée dans un pool pour que d'autres puissent réutiliser? Regardez le code de MainclientExec
// Envoyer une réponse de connexion HTTP = requestExecutor.Execute (request, ManagedConn, context); // défendez si la connexion actuelle doit être réutilisée en fonction de la stratégie de réutilisation if (reusestrategy.keepalive (réponse, contexte)) {// La connexion qui doit être réutilisée, obtenez le temps de temps de la connexion, en fonction du délai d'expiration dans la réponse finale de la réponse = keepalivestrategy.getkeepaliveduration (réponse, contexte); if (this.log.isdebugeNabled ()) {final String S; // Timeout est le nombre de millisecondes, si ce n'est pas défini, il est -1, c'est-à-dire qu'il n'y a pas de délai de délai si (durée> 0) {s = "pour" + durée + "" + timeunit.milliseconds; } else {s = "indéfiniment"; } this.log.debug ("La connexion peut être maintenue en vie" + s); } // Définissez l'heure du délai d'expiration. À la fin de la demande, le gestionnaire de connexion décidera de le fermer ou de le remettre dans le pool en fonction du temps mort ConnHolder.SetValidFor (durée, timeUnit.Millisecondes); // Inscrivez la connexion en tant que ConnHolder réutilisable.Markreusable (); } else {// Inscrivez la connexion en tant que ConnHolder non réutilisable.MarkNonReusable (); }On peut voir qu'après une demande en utilisant une connexion, il existe une politique de réessayer de connexion pour décider si la connexion doit être réutilisée. S'il est réutilisé, il sera remis au HttpClientConnectionManager après la fin.
Alors, quelle est la logique de la politique de multiplexage des connexions?
classe publique defaultClientConnectionReuseStrategy étend defaultConnectionReUsestrategy {public static finalclientConnectionDeUseStrategy instance = new defaultClientConnectionReuseStrategy (); @Override public boolean keepalive (final httpResponse réponse, final httpcontext context) {// get request request httpRequest à partir du contexte final httprequest request = (httprequest) context.getAttribute (httpcoreContext.http_request); if (request! = null) {// Obtenez l'en-tête final de l'en-tête de la connexion [] Connheders = request.Getheaders (httpheaders.connection); if (Connheders.Length! = 0) {final tokeniterator ti = new BasicTokeniterator (new BasicheDeriterator (Connheders, null)); while (ti.hasnext ()) {final String token = ti.nextToken (); // Si la connexion: l'en-tête de fermeture est incluse, cela signifie que la demande n'a pas l'intention de conserver la connexion et l'intention de la réponse sera ignorée. Cet en-tête est la spécification de http / 1.1 if (http.conn_close.equalsignorecase (token)) {return false; }}}}} // Utilisez la stratégie de réutilisation de la classe parent pour retourner super.keekeve (réponse, contexte); }}Jetez un œil à la stratégie de réutilisation de la classe des parents
if (canSponseHaveBody (request, réponse)) {en-tête final [] clhs = réponse.GetHeaders (http.content_len); // Si la longueur de contenu de la réponse n'est pas définie correctement, la connexion ne sera pas réutilisée // Parce que pour les connexions persistantes, il n'est pas nécessaire de rétablir la connexion entre les deux transmissions, vous devez confirmer à quelle demande de contenu appartient à la longueur du contenu pour gérer correctement la longueur du contenu ne peut pas être réutilisé si " clh = 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 (HereterIterator.Hasnext ()) {try {final tokeniterator ti = new BasIctOKINITERATOR (HeaderIterator); booléen keepalive = false; while (ti.hasnext ()) {final String token = ti.nextToken (); // Si la réponse a une connexion: fermeture, il est explicitement indiqué qu'il doit être fermé, et if (http.conn_close.equalsignorecase (token)) {return false; // Si la réponse a un en-tête de connexion: Keep-Alive, il est explicitement indiqué qu'il doit être persisté, il est réutilisé} else if (http.conn_keep_alive.equalsignorecase (token)) {keepalive = true; }} if (keepalive) {return true; }} catch (final parseException px) {return false; }}} // S'il n'y a pas de description de l'en-tête de connexion pertinente dans la réponse, toutes les connexions supérieures aux versions HTTP / 1.0 seront réutilisées pour retourner! Ver.lessEquals (httpversion.http_1_0);Pour résumer:
Comme on peut le voir dans le code, sa stratégie de mise en œuvre est conforme aux contraintes de nos couches de protocole des chapitre 2 et du chapitre 3.
6. Comment nettoyer les connexions expirées de HttpClient
Avant HttpClient 4.4, lors de la réutilisation de la connexion à partir du pool de connexion, il vérifiera s'il expire et le nettoiera s'il expire.
La version suivante sera différente. Il y aura un thread séparé pour scanner les connexions dans le pool de connexions. Après avoir constaté qu'il y a une dernière utilisation du temps qui a été fixé, il sera nettoyé. Le délai d'expiration par défaut est de 2 secondes.
Public CloseableHttpClient build () {// Si vous spécifiez que vous souhaitez nettoyer les connexions expirées et inactives, le fil de nettoyage sera démarré. La valeur par défaut n'est pas démarrée si (evictExpiredConnections || evictidleConnections) {// créer un thread propre pour un pool de connexion final idleConnectionEvictor connectionEvictor = new IdleConnectionEvictor (CM, maxidleTime> 0? MaxidleTime: 10, maxidleTimeUnit! = Null? MaxidleTimeUnit: TimeLnit.Seconds, MAXIDIMETIME? MaxidleTimeUnit); CloseAblesCopy.Add (new Closable () {@Override public void close () lance ioException {ConnectionEvictor.shutdown (); try {ConnectionEvictor.AwitterMination (1L, TimeUnit.seconds);} Catch (InterruptedException final Interrupted) {thread.currentThread (). Interrupt ();}}); // Exécuter le thread de nettoyage Connectionevictor.Start ();}Vous pouvez voir que lorsque HttpClientBuilder se construit, si la fonction de nettoyage est activée, un fil de nettoyage de pool de connexion sera créé et l'exécutera.
public idleConnectionEvictor (final httpclientConnectionManager ConnectionManager, final Threadfactory ThreadFactory, final long temps de sommeil, final TimeUnit 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 (temps de sommeil): temps de sommeil; this.MaXidleTimems = maxidleTimeUnit! = null? MaxidleTimeUnit.tomiillis (MaxidleTime): MaxidleTime; this.thread = this.threadfactory.newthread (new Runnable () {@Override public void run () {try {// la boucle morte, le thread continue d'exécuter tandis que (! thread.currentThread (). Isterrupted ()) {// Execute après quelques secondes de repos, par défaut vers 10 secondes. ConnectionManager.CloseExpiredConnections (); }Pour résumer:
7. Résumé de cet article
La recherche ci-dessus est basée sur la compréhension personnelle du code source HTTPClient. S'il y a une erreur, j'espère que tout le monde laissera un message pour en discuter activement.
D'accord, ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.