1. Hintergrund
Das HTTP -Protokoll ist ein zustandsloses Protokoll, dh jede Anfrage ist unabhängig voneinander. Daher ist die erste Implementierung, dass jede HTTP -Anforderung eine TCP -Socket -Verbindung öffnet und die Verbindung nach Abschluss der Interaktion geschlossen wird.
Das HTTP -Protokoll ist ein volles Duplexprotokoll, sodass drei Händedruck und vier Wellen zum Festlegen und Trennen benötigt werden. In diesem Design konsumiert sie jedes Mal, wenn ich eine HTTP -Anfrage sende, viele zusätzliche Ressourcen, nämlich die Einrichtung und Zerstörung der Verbindung.
Daher wurde auch das HTTP -Protokoll entwickelt, und Socket -Verbindungs -Multiplexing wird durch anhaltende Verbindungsmethoden durchgeführt.
Auf dem Bild können Sie sehen:
Es gibt zwei Implementierungen anhaltender Verbindungen: Keep-Alive- und anhaltende Verbindungen von HTTP/1.1 für HTTP/1.0+.
2. Keep-Alive für http/1.0+
Seit 1996 haben viele HTTP/1,0-Browser und Server das Protokoll erweitert, dh das "Keep-Alive" -Lehnungsprotokoll.
Beachten Sie, dass dieses Erweiterungsprotokoll als Komplement zu 1.0 "Experimental Persistent Connection" erscheint. Keep-Alive wird nicht mehr verwendet und wird in der neuesten HTTP/1.1-Spezifikation nicht erklärt, aber viele Anwendungen haben sich fortgesetzt.
Clients verwenden HTTP/1.0 "Verbindung: Keep-Alive" zum Header und fordern Sie den Server an, eine Verbindung geöffnet zu halten. Wenn der Server bereit ist, diese Verbindung offen zu halten, enthält er denselben Kopfzeilen in die Antwort. Wenn die Antwort nicht den Header "Verbindung: Keep-Alive" enthält, wird der Client der Meinung, dass der Server nicht die Keep-Alive unterstützt und die aktuelle Verbindung nach dem Senden der Antwortnachricht schließt.
Durch das Zusatzprotokoll in Keep-Alive wird eine anhaltende Verbindung zwischen dem Client und dem Server abgeschlossen, aber es gibt immer noch einige Probleme:
3.. Persistierende Verbindung von HTTP/1.1
HTTP/1.1 nimmt eine anhaltende Verbindungsmethode an, um die Keep-Alive zu ersetzen.
HTTP/1.1 Verbindungen sind standardmäßig anhaltend. Wenn Sie explizit schließen möchten, müssen Sie die Verbindung hinzufügen: Header zur Nachricht schließen. Das heißt, in HTTP/1.1 werden alle Verbindungen multiplexiert.
Wie bei Keep-Alive können dauerhafte Verbindungen jedoch jederzeit vom Client und dem Server geschlossen werden. Nicht senden Verbindung: Schließen bedeutet nicht, dass der Server verspricht, dass die Verbindung für immer geöffnet bleibt.
V.
HTTPCLIEN verwendet einen Verbindungspool, um die Halteverbindungen zu verwalten. Auf dem gleichen TCP -Link können Verbindungen wiederverwendet werden. Httpclient verbindet die Persistenz durch Verbindungspooling.
Tatsächlich ist die "Pool" -Technologie ein allgemeines Design und seine Designidee ist nicht kompliziert:
Alle Verbindungspools haben diese Idee, aber wenn wir uns den HTTPClient -Quellcode ansehen, konzentrieren wir uns hauptsächlich auf zwei Punkte:
4.1 Implementierung des HTTPClient -Verbindungspools
Die Verarbeitung persistierender Verbindungen durch HTTPClient kann im folgenden Code widerspiegelt werden. Im Folgenden wird die Teile des Verbindungspools aus MainclientExec extrahiert und andere Teile entfernen:
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 endgültig ConnectionRequest connRequest = connManager.requestConnection (Route, UserToken); endgültig httpclientConnection ManagedConn; endgültig int timeout = config.getConnectionRequestTimeout (); // eine verwaltete Verbindung von der Verbindungsanforderung Connection -RequestresttpclientConnection ManagedConn = connRequest.get (Timeout> 0? Timeout: 0, Timeunit.Milliseconds) erhalten; // Senden Sie den Connection Manager httpclientConnectionManager und die verwaltete Verbindung httpclientConnection an einen Anbieter, der den endgültigen Anbieter Connholder = neuer Anbieter besitzt (this.log, this.connmanager, ManagedConn); Versuchen Sie {httPesponse -Antwort; if (! ManagedConn.ISopen ()) {// Wenn die derzeit verwaltete Verbindung nicht in einem offenen Zustand ist, müssen Sie die festgelegte Verbindung wiederherstellen (ProxyAuthstate, ManagedConn, Route, Anfrage, Kontext); } // Anforderung über die Verbindung senden httpclientConnection response = RequestExecutor.execute (Anfrage, ManagedConn, Kontext); // Unterscheiden Sie, ob die Verbindung durch die Verbindungs -Wiederverwendung -Strategie wiederverwendet werden kann, wenn (reusestrategy.Keepalive (Antwort, Kontext)) {// die Verbindungsgültigkeitszeit endgültig lange Dauer = Keepalivestrategy.Getkeepaliveduration (Antwort, Kontext) erhalten; // Setzen Sie die Anschlussgültigkeitsdauer Conneholder.setValidfor (Dauer, TimeUnit.Milliseconds); // markieren Sie die aktuelle Verbindung als wiederverwendbarer Staat Conneholder.MarkReuseable (); } else {connholder.marknonReusable (); }} endgültig httpentity entity = response.getEntity (); if (entity == null ||! entity.ISStreaming ()) {// die aktuelle Verbindung zum Pool für den nächsten Anruf an connHolder.releaseConnection () freigeben; Return New HttPresponseproxy (Response, NULL); } else {return New httPresponseproxy (Antwort, Connholder); }}Hier sehen wir, dass die Verbindungsverarbeitung während des HTTP -Anforderungsprozesses mit den Protokollspezifikationen übereinstimmt. Hier werden wir die spezifische Implementierung diskutieren.
PoolinghttpclientConnectionManager ist der Standardverbindungs -Manager von HTTPClient. Ermitteln Sie zunächst eine Verbindungsanforderung über RequestConnection (). Beachten Sie, dass dies keine Verbindung ist.
public ConnectionRequest RequestConnection (endgültige HTTProute -Route, endgültiger Objektstaat) {endgültige Zukunft <cpoolEnry> Future = this.pool.Lease (Route, State, Null); return new ConnectionRequest () {@Override public boolean cancel () {return future.cancel (true); } @Override public httpclientConnection Get (endgültige Langzeit -Timeout, endgültige Zeiteinheit) löst InterruptedException, ExecutionException, ConnectionPoolTimeOutException aus {endgültig httpclientConnection conn = LeaseConnection (Future, Timeout, Tunit); if (conn.isopen ()) {endgültiger httphost Host; if (Route.getProxyHost ()! = null) {host = Route.getProxyHost (); } else {host = Route.gettargethost (); } Final SocketConfig SocketConfig = ResolsocketConfig (Host); Conn.Setsockettimeout (SocketConfig.getSotimeout ()); } return conn; }}; }Sie können sehen, dass das zurückgegebene ConnectionRequest -Objekt tatsächlich eine echte Verbindungsinstanz ist, die zukünftige <cpoolentry> hält, die vom Verbindungspool verwaltet wird.
Aus dem obigen Code sollten wir uns konzentrieren:
Future<CPoolEntry> future = this.pool.lease(route, state, null)
So erhalten Sie eine asynchrone Verbindung von einem Cpool -Verbindungspool, Future <CpoolEntry>
HttpClientConnection conn = leaseConnection(future, timeout, tunit)
So erhalten Sie eine echte Verbindung durch asynchrone Verbindung zu Future <Cpoolentry>
4.2 Future <CpoolEntry>
Schauen wir uns an, wie cpool eine zukünftige <cpoolEntry> veröffentlicht. Der Kerncode von AbstractConnpool lautet wie folgt:
Private E GetpoolEnryBlocking (endgültige T -Route, endgültiger Objektzustand, endgültige lange Zeit, endgültige Zeiteinheit, endgültige Zukunft <e> Zukunft) löst IOException, InterruptedException, TimeoutException aus {// Erst den aktuellen Verbindungspool sperren. Das aktuelle Schloss ist ein Reentrantlockthis.lock.lock (); Versuchen Sie es mit {// einen Verbindungspool, der dem aktuellen httproute entspricht. Für den Verbindungspool von httpclient hat der Gesamtpool eine Größe und die zu jeder Route entsprechende Verbindung ist ebenfalls ein Pool. Daher handelt es sich um einen "Pool im Pool" endgültigrotenspezifischpool <T, C, E> Pool = getPool (Route); E Eintrag; für (;;) {Asserts.Check (! this.isshutdown, "Verbindungspool schaltet"); // Verbindungen aus dem Pool erhalten, die der Route entsprechen, die null sein kann, oder ein gültiger Verbindungseintrag = Pool.GetFree (Status); // Wenn Sie NULL erhalten, beenden Sie die Schleife, wenn (Eintrag == null) {Break; } // Wenn Sie eine abgelaufene Verbindung erhalten oder die Verbindung geschlossen wurde, geben Sie die Ressource frei und schleifen Sie weiter, um zu erhalten, ob (ISEXPired (System.currentTimememillis ()) {Eintrag.CLOSE (); } if (Eintrag. Pool.Free (Eintrag, Falsch); } else {// Wenn Sie eine gültige Verbindung erhalten, beenden Sie den Schleifenbruch. }} // Wenn Sie eine gültige Verbindung erhalten, beenden Sie, wenn (Eintrag! = Null) {this.available.remove (Eintrag); this.theased.add (Eintrag); Onreeuse (Eintrag); Rückgabe; } // Um hier zu beweisen, dass keine gültige Verbindung erhalten wurde, müssen Sie eine endgültige int maxperroute = getmax (Route) generieren; // Die maximale Anzahl von Verbindungen, die jeder Route entsprechen, ist konfigurierbar. Wenn es überschreitet, müssen Sie einige Verbindungen über LRU Final INT exess = math.max (0, Pool.GetAllocatedCount () + 1 - MaxperRoute) aufräumen. if (überschüssig> 0) {für (int i = 0; i <überschüssig; i ++) {Final e lastused = pool.getLastused (); if (lastused == null) {break; } lastused.close (); this.Available.remove (zuletzt verwendet); pool.remove (zuletzt verwendet); }} // Die Anzahl der Verbindungen im aktuellen Routenpool hat das Online -if nicht erreicht (pool.getallocatedCount () <maxperRoute) {endgültig int Totalused = this.tlease.size (); endgültige int freecapacity = math.max (this.maxtotal - totalused, 0); // Beurteilen Sie, ob der Verbindungspool die Online -Linie überschreitet. Wenn es überschreitet, müssen Sie einige Verbindungen über LRU aufräumen, wenn (Freecapacity> 0) {endgültig int TotalAbleable = this.available.size (); // Wenn die Anzahl der kostenlosen Verbindungen bereits größer ist als der verbleibende verfügbare Speicherplatz, müssen Sie die kostenlose Verbindung aufräumen, wenn (TotalAbableable> Freecapacity - 1) {if (! This.Available.isEmpty ()) {endgültig e lastused = this.available.removelast (); letztes verwendet.CLOSE (); endgültige Routesspecificpool <t, c, e> otherpool = getPool (lastUts.getRoute ()); otherpool.remove (zuletzt); }} // Erstellen Sie eine Verbindung basierend auf der Route endgültig c conn = this.connfactory.create (Route); // diese Verbindung in den "kleinen Pool" eingeben, der dem Routeneintrag = pool.add (conn) entspricht; // diese Verbindung in den "großen Pool" eingeben. Rückgabe; }} // Zu diesem Zweck wird bewiesen, dass es keine gültige Verbindung aus dem Routenpool gibt, und wenn Sie selbst eine Verbindung herstellen möchten, hat der aktuelle Routenverbindungspool seinen maximalen Wert erreicht, dh bereits eine Verbindung, die verwendet wird, aber für den aktuellen Thread Boolean Success = False; try {if (future.iscancelled ()) {neue InterruptedException ("Operation unterbrochen"); } // Future in den Routenpool aufpool.queue (Future); // Zukünftige in den großen Verbindungspool auf diese. // Wenn Sie auf die Benachrichtigung des Semaphors warten, ist der Erfolg wahr, wenn (Deadline! } else {this.condition.await (); Erfolg = wahr; } if (future.iscancelled ()) {neue InterruptedException ("Operation unterbrochen"); }} endlich {// entfernen Sie pool.unqueue (future); this.pending.remove (Zukunft); } // Wenn die Semaphor -Benachrichtigung nicht gewartet wird und die aktuelle Zeit abgelaufen ist, wird die Schleife beendet, wenn (! Erfolg && (Deadline! }} // am Ende wurde keine Semaphor -Benachrichtigung erhalten und es wurde keine verfügbare Verbindung erhalten, eine Ausnahme wurde geworfen. Neue TimeoutException ("Timeout auf Verbindung") werfen; } endlich {// die Sperre auf dem großen Verbindungspool this.lock.unlock () freigeben; }}In der obigen Codelogik gibt es mehrere wichtige Punkte:
Bisher hat das Programm eine verfügbare CpoolEnry -Instanz erhalten oder das Programm durch Ausnahme einer Ausnahme beendet.
4.3 httpclientConnection
Protected HttpclientConnection LeaSeconnection (endgültige Zukunft <CpoolEntry> zukünftige, endgültige lange ZeitOut, endgültige Timeinit) löst InterruptedException, ExecutionException, ConnectionPoolTimeoutException aus {endgültiger CpoolEnry -Eintrag; Versuchen Sie {// CpoolEnry -Eintrag von Asynchronous Operation Future <CpoolEntry> Eintrag = Future.get (Timeout, Tunit); if (Eintrag == null || future.iscancelled ()) {throw New InterruptedException (); } Asserts.Check (Eintrag.getConnection ()! = NULL, "Pooleintrag ohne Verbindung"); if (this.log.isdebugenabled ()) {this.log.debug ("Verbindung verschont:" + Format (Eintrag) + FormatStats (Eintrag.GetRoute ())); } // Erhalten Sie ein Proxy -Objekt von cpoolEntry, und die Operationen werden mit demselben zugrunde liegenden httpclientConnection -Return cpoolproxy.newProxy (Eintrag) durchgeführt. } catch (endgültige TimeoutException ex) {Neue ConnectionPoolTimeOutException ("Timeout wartet auf die Verbindung vom Pool"); }} 5. Wie kann man anhaltende Verbindungen in httpclient wiederverwenden?
Im vorherigen Kapitel haben wir gesehen, dass HTTPClient Verbindungen über Verbindungspools erhält und sie aus dem Pool bekommt, wenn es erforderlich ist, Verbindungen zu verwenden.
Entsprechend dem dritten Kapitel:
In Kapitel 4 haben wir gesehen, wie Httpclient mit den Problemen von 1 und 3 umgeht. Wie gehen wir also mit der zweiten Frage um?
Wie bestimmt HTTPClient, ob eine Verbindung nach dem Gebrauch geschlossen werden sollte oder sollte sie in einem Pool platziert werden, damit andere wiederverwendet werden können? Schauen Sie sich den Code von mellisexec an
// HTTP -Verbindungsantwort senden = RequestExecutor.execute (Anfrage, ManagedConn, Kontext); // verteidigen, ob die aktuelle Verbindung auf der Grundlage der Wiederverwendungstrategie wiederverwendet werden soll, wenn (reusestrategy.Keepalive (Antwort, Kontext)) {// die Verbindung, die wiederverwendet werden muss, die Verbindungszeitüberschreitungszeit auf der Grundlage der Zeitüberschreitung in der Antwort endgültig duration = keepalivestrategy.getegy. if (this.log.isdebugenabled ()) {Final String S; // Timeout ist die Anzahl der Millisekunden, wenn nicht festgelegt, es ist -1, dh es gibt keine Zeitüberschreitung, wenn (Dauer> 0) {S = "für" + Dauer + "" + TimeUnit.Milliseconds; } else {s = "unbegrenzt"; } this.log.debug ("Verbindung kann lebendig gehalten werden" + s); } // Legen Sie die Zeitüberschreitungszeit fest. Wenn die Anfrage endet, entscheidet der Verbindungsmanager, ob er basierend auf der Zeitlimitzeit Conneholder.setValidfor (Dauer, Zeiteinheit.Millisekunden) schließen oder wieder in den Pool bringen soll. // Die Verbindung als wiederverwendbares Conneholder.MarkReusable () anmelden; } else {// Anmelden Sie die Verbindung als nicht wiederverwendbarer Connholder.MarknonReusable (); }Es ist ersichtlich, dass nach einer Anfrage mithilfe einer Verbindung eine Richtlinie für Verbindungen wiederholt wird, um zu entscheiden, ob die Verbindung wiederverwendet werden soll. Wenn es wiederverwendet wird, wird es nach dem Ende an den httpclientConnectionManager übergeben.
Wie lautet die Logik der Verbindungsmultiplex -Richtlinie?
Public Class DefaultClientConnectionReusestrategy erweitert DefaultConnectionReusestrategy {öffentliche statische endgültige defaultConnectionReusestrategy Instance = new DefaultClientConnectionReusestrategy (); @Override public boolean ketealive (endgültige httpresponse -Antwort, endgültiger httpcontext context) {// Anforderung endgültig httprequest aus dem Kontext endgültig httprequest request = (httprequest) context.getAttribute (httpcorecontext.http_request); if (request! if (connheaders.length! while (ti.hasnext ()) {Final String token = ti.NextToken (); // Wenn die Verbindung: Abschluss des Headers enthalten ist, bedeutet dies, dass die Anfrage nicht beabsichtigt, die Verbindung zu behalten, und die Absicht der Antwort wird ignoriert. Dieser Header ist die Spezifikation von http/1.1 if (http.conn_close.equalSignoreCase (Token)) {return false; }}}}} // Verwenden Sie die Wiederverwendungstrategie der übergeordneten Klasse, um Super.Keepalive (Antwort, Kontext) zurückzugeben; }}Schauen Sie sich die Wiederverwendungsstrategie der Elternklasse an
if (canResponseHaveBody (Anfrage, Antwort)) {endgültiger Header [] clhs = response.getheaders (http.content_len); // Wenn die inhaltliche Länge der Antwort nicht korrekt eingestellt ist, wird die Verbindung nicht wiederverwendet // Da für anhaltende Verbindungen die Verbindung zwischen den beiden Getriebe nicht wieder hergestellt werden müssen, müssen Sie bestätigen, welcher Anforderung der Inhalt basierend auf der Inhaltslänge basiert, um die Länge "Stickpaket" zu verarbeiten. = clhs [0]; try {Final int contentlen = integer.parseInt (clh.getValue ()); if (contentlen <0) {return false; }} catch (endgültige numberformatexception ex) {return false; }} else {return false; }} if (Headeriterator. boolean keepalive = false; while (ti.hasnext ()) {Final String token = ti.NextToken (); // Wenn die Antwort eine Verbindung hat: Header schließen, wird ausdrücklich angegeben, dass sie geschlossen werden soll, und wenn (http.conn_close.equalSignoreCase (Token)) {return false; // Wenn die Antwort eine Verbindung hat: Keep-Alive-Header, wird ausdrücklich angegeben, dass sie bestehen bleibt. Es wird wiederverwendet. }} if (keepalive) {return true; }} catch (endgültige parseException px) {return false; }}} // Wenn in der Antwort keine relevante Verbindungsheaderbeschreibung vorhanden ist, werden alle Verbindungen höher als HTTP/1.0 -Versionen zur Rückgabe wiederverwendet! Ver.LessEllequals (httpversion.http_1_0);Zusammenfassen:
Wie aus dem Code ersichtlich ist, stimmt seine Implementierungsstrategie mit den Einschränkungen unserer Protokollebenen in Kapitel 2 und Kapitel 3 überein.
6. So reinigen Sie abgelaufene Verbindungen von httpclient
Vor Httpclient 4.4 wird bei der Wiederverwendung der Verbindung aus dem Verbindungspool überprüft, ob er abläuft, und reinigen, wenn er abläuft.
Die nachfolgende Version wird unterschiedlich sein. Es gibt einen separaten Faden zum Scannen der Verbindungen im Verbindungspool. Nachdem festgestellt wurde, dass die Zeit, die festgelegt wurde, eine letzte Nutzung gibt, wird es aufgeräumt. Das Standard -Timeout beträgt 2 Sekunden.
public CloseableHttpclient Build () {// Wenn Sie angeben, dass Sie abgelaufene und Leerlaufanschlüsse reinigen möchten, wird der Reinigungsgewinde gestartet. Der Standardwert wird nicht gestartet, wenn (evictexpiredConnections || evictIdleconnections) {// einen sauberen Thread für einen Verbindungspool endgültig erstellen. Maxidletime, MaxidletimeUnit); CloseAblesCopy.Add (new Closeable () {@Override public void close () löst ioException {ConnectionEvictor.shutdown (); try {ConnectionEvictor.Awaittermination (1l, Timeunit.seconds);} catch (endgültige InterruptedException Interrupted) {Thread.curent ().). // Führen Sie den Reinigungs -Thread Connection evictor.start () aus;};};Sie können sehen, dass beim Aufbau von HTTPClientBuilder, wenn die Reinigungsfunktion aktiviert ist, ein Anschlusspool -Reinigungsgewinde erstellt und ausgeführt wird.
public idleconnectionevictor (endgültig httpclientConnectionManager ConnectionManager, endgültiger ThreadFaktorische Threadfaktor, endgültige Long Sleeptime, Final Timeunit SleepTimeUnit, endgültiger langes Maxidletime, endgültiges ZeitUnit MaxidLetimeUnit) {this.connectionManager = args .notnull (connectionManager (connectionManager); this.threadfactory = threadFactory! = null? ThreadFactory: New default threadfactory (); this.sleeptimemems = sleepTimeUnit! = null? SleepTimeUnit.Tomillis (Schlafzeit): Schlafzeit; this.maxidletImemems = maxidletimeUnit! = null? maxidletimeUnit.tomillis (maxidletime): maxidletime; this.thread = this.threadfactory.newThread (new Runnable () {@Override public void run () {try {// Die tote Schleife wird weiter ausgeführt, während (! Thread.CurrentThread (). isinterbrust ()) {// Ausführende Ausführung nach ein paar Seconds, die bis zu 10 Sekunden -Threads -Threads -Threads (Sleeptimes); ConnectionManager.CLOSEExpiredConnections (); }Zusammenfassen:
7. Zusammenfassung dieses Artikels
Die obige Forschung basiert auf dem persönlichen Verständnis des HTTPClient -Quellcodes. Wenn es einen Fehler gibt, hoffe ich, dass jeder eine Nachricht hinterlässt, um sie aktiv zu besprechen.
Okay, das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Referenzwert für das Studium oder die Arbeit eines jeden hat. Wenn Sie Fragen haben, können Sie eine Nachricht zur Kommunikation überlassen. Vielen Dank für Ihre Unterstützung bei Wulin.com.